只读模式
group, 分享
评论更多问题
博客标签总是存在一个
This commit is contained in:
lealife
2015-06-15 18:01:48 +08:00
parent 7e458bb433
commit 6987a38820
1453 changed files with 114561 additions and 91536 deletions

377
public/tinymce/classes/Formatter.js Normal file → Executable file
View File

@ -25,8 +25,11 @@
define("tinymce/Formatter", [
"tinymce/dom/TreeWalker",
"tinymce/dom/RangeUtils",
"tinymce/util/Tools"
], function(TreeWalker, RangeUtils, Tools) {
"tinymce/dom/BookmarkManager",
"tinymce/dom/ElementUtils",
"tinymce/util/Tools",
"tinymce/fmt/Preview"
], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) {
/**
* Constructs a new formatter instance.
*
@ -50,7 +53,8 @@ define("tinymce/Formatter", [
undef,
getContentEditable = dom.getContentEditable,
disableCaretContainer,
markCaretContainersBogus;
markCaretContainersBogus,
isBookmarkNode = BookmarkManager.isBookmarkNode;
var each = Tools.each,
grep = Tools.grep,
@ -75,6 +79,18 @@ define("tinymce/Formatter", [
function defaultFormats() {
register({
valigntop: [
{selector: 'td,th', styles: {'verticalAlign': 'top'}}
],
valignmiddle: [
{selector: 'td,th', styles: {'verticalAlign': 'middle'}}
],
valignbottom: [
{selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
],
alignleft: [
{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
{selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
@ -117,8 +133,8 @@ define("tinymce/Formatter", [
{inline: 'strike', remove: 'all'}
],
forecolor: {inline: 'span', styles: {color: '%value'}, wrap_links: false},
hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, wrap_links: false},
forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true},
hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true},
fontname: {inline: 'span', styles: {fontFamily: '%value'}},
fontsize: {inline: 'span', styles: {fontSize: '%value'}},
fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
@ -141,7 +157,8 @@ define("tinymce/Formatter", [
removeformat: [
{
selector: 'b,strong,em,i,font,u,strike,sub,sup',
// life
selector: 'h1,h2,h3,h4,h5,h6,pre,b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins',
remove: 'all',
split: true,
expand: false,
@ -164,18 +181,18 @@ define("tinymce/Formatter", [
function addKeyboardShortcuts() {
// Add some inline shortcuts
ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
ed.addShortcut('meta+b', 'bold_desc', 'Bold');
ed.addShortcut('meta+i', 'italic_desc', 'Italic');
ed.addShortcut('meta+u', 'underline_desc', 'Underline');
// BlockFormat shortcuts keys
for (var i = 1; i <= 6; i++) {
ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
ed.addShortcut('meta+shift+' + i, '', ['FormatBlock', false, 'h' + i]);
}
ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
ed.addShortcut('meta+shift+7', '', ['FormatBlock', false, 'p']);
ed.addShortcut('meta+shift+8', '', ['FormatBlock', false, 'div']);
ed.addShortcut('meta+shift+9', '', ['FormatBlock', false, 'address']);
}
// Public functions
@ -201,7 +218,7 @@ define("tinymce/Formatter", [
*/
function register(name, format) {
if (name) {
if (typeof(name) !== 'string') {
if (typeof name !== 'string') {
each(name, function(format, name) {
register(name, format);
});
@ -233,7 +250,7 @@ define("tinymce/Formatter", [
}
// Split classes if needed
if (typeof(format.classes) === 'string') {
if (typeof format.classes === 'string') {
format.classes = format.classes.split(/\s+/);
}
});
@ -243,6 +260,20 @@ define("tinymce/Formatter", [
}
}
/**
* Unregister a specific format by name.
*
* @method unregister
* @param {String} name Name of the format for example "bold".
*/
function unregister(name) {
if (name && formats[name]) {
delete formats[name];
}
return formats;
}
function getTextDecoration(node) {
var decoration;
@ -260,7 +291,7 @@ define("tinymce/Formatter", [
textDecoration = getTextDecoration(node.parentNode);
if (ed.dom.getStyle(node, 'color') && textDecoration) {
ed.dom.setStyle(node, 'text-decoration', textDecoration);
} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
} else if (ed.dom.getStyle(node, 'text-decoration') === textDecoration) {
ed.dom.setStyle(node, 'text-decoration', null);
}
}
@ -289,6 +320,16 @@ define("tinymce/Formatter", [
dom.setStyle(elm, name, replaceVars(value, vars));
});
// Needed for the WebKit span spam bug
// TODO: Remove this once WebKit/Blink fixes this
if (fmt.styles) {
var styleVal = dom.getAttrib(elm, 'style');
if (styleVal) {
elm.setAttribute('data-mce-style', styleVal);
}
}
each(fmt.attributes, function(value, name) {
dom.setAttrib(elm, name, replaceVars(value, vars));
});
@ -329,62 +370,6 @@ define("tinymce/Formatter", [
return rng;
}
function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
// find the index of the first child list.
each(node.childNodes, function(n, index) {
if (n.nodeName === "UL" || n.nodeName === "OL") {
listIndex = index;
list = n;
return false;
}
});
// get the index of the bookmarks
each(node.childNodes, function(n, index) {
if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
if (n.id == bookmark.id + "_start") {
startIndex = index;
} else if (n.id == bookmark.id + "_end") {
endIndex = index;
}
}
});
// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
each(grep(node.childNodes), process);
return 0;
} else {
currentWrapElm = dom.clone(wrapElm, FALSE);
// create a list of the nodes on the same side of the list as the selection
each(grep(node.childNodes), function(n, index) {
if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
nodes.push(n);
n.parentNode.removeChild(n);
}
});
// insert the wrapping element either before or after the list.
if (startIndex < listIndex) {
node.insertBefore(currentWrapElm, list);
} else if (startIndex > listIndex) {
node.insertBefore(currentWrapElm, list.nextSibling);
}
// add the new nodes to the list.
newWrappers.push(currentWrapElm);
each(nodes, function(node) {
currentWrapElm.appendChild(node);
});
return currentWrapElm;
}
}
function applyRngStyle(rng, bookmark, node_specific) {
var newWrappers = [], wrapName, wrapElm, contentEditable = true;
@ -481,10 +466,6 @@ define("tinymce/Formatter", [
}
currentWrapElm.appendChild(node);
} else if (nodeName == 'li' && bookmark) {
// Start wrapping - if we are in a list node and have a bookmark, then
// we will always begin by wrapping in a new element.
currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
} else {
// Start a new wrapper for possible children
currentWrapElm = 0;
@ -504,22 +485,12 @@ define("tinymce/Formatter", [
each(nodes, process);
});
// Wrap links inside as well, for example color inside a link when the wrapper is around the link
if (format.wrap_links === false) {
// Apply formats to links as well to get the color of the underline to change as well
if (format.links === true) {
each(newWrappers, function(node) {
function process(node) {
var i, currentWrapElm, children;
if (node.nodeName === 'A') {
currentWrapElm = dom.clone(wrapElm, FALSE);
newWrappers.push(currentWrapElm);
children = grep(node.childNodes);
for (i = 0; i < children.length; i++) {
currentWrapElm.appendChild(children[i]);
}
node.appendChild(currentWrapElm);
setElementFormat(node, format);
}
each(grep(node.childNodes), process);
@ -556,7 +527,7 @@ define("tinymce/Formatter", [
});
// If child was found and of the same type as the current node
if (child && matchName(child, format)) {
if (child && !isBookmarkNode(child) && matchName(child, format)) {
clone = dom.clone(child, FALSE);
setElementFormat(clone);
@ -589,18 +560,8 @@ define("tinymce/Formatter", [
// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
each(dom.select(format.inline, node), function(child) {
var parent;
// When wrap_links is set to false we don't want
// to remove the format on children within links
if (format.wrap_links === false) {
parent = child.parentNode;
do {
if (parent.nodeName === 'A') {
return;
}
} while ((parent = parent.parentNode));
if (isBookmarkNode(child)) {
return;
}
removeFormat(format, vars, child, format.exact ? child : null);
@ -685,7 +646,7 @@ define("tinymce/Formatter", [
* @param {Object} vars Optional list of variables to replace within format before removing it.
* @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
*/
function remove(name, vars, node) {
function remove(name, vars, node, similar) {
var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
// Merges the styles for each node
@ -735,7 +696,7 @@ define("tinymce/Formatter", [
// Find format root element
if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
// Is the node matching the format we are looking for
format = matchNode(parent, name, vars);
format = matchNode(parent, name, vars, similar);
if (format && format.split !== false) {
formatRoot = parent;
}
@ -745,12 +706,12 @@ define("tinymce/Formatter", [
return formatRoot;
}
function wrapAndSplit(format_root, container, target, split) {
function wrapAndSplit(formatRoot, container, target, split) {
var parent, clone, lastClone, firstClone, i, formatRootParent;
// Format root found then clone formats and split it
if (format_root) {
formatRootParent = format_root.parentNode;
if (formatRoot) {
formatRootParent = formatRoot.parentNode;
for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
clone = dom.clone(parent, FALSE);
@ -777,8 +738,8 @@ define("tinymce/Formatter", [
}
// Never split block elements if the format is mixed
if (split && (!format.mixed || !isBlock(format_root))) {
container = dom.split(format_root, container);
if (split && (!format.mixed || !isBlock(formatRoot))) {
container = dom.split(formatRoot, container);
}
// Wrap container in cloned formats
@ -806,6 +767,11 @@ define("tinymce/Formatter", [
out = out[start ? 'firstChild' : 'lastChild'];
}
// Since dom.remove removes empty text nodes then we need to try to find a better node
if (out.nodeType == 3 && out.data.length === 0) {
out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling;
}
dom.remove(node, true);
return out;
@ -856,9 +822,9 @@ define("tinymce/Formatter", [
}
// Update range positions since they might have changed after the split operations
rng.startContainer = startContainer.parentNode;
rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer;
rng.startOffset = nodeIndex(startContainer);
rng.endContainer = endContainer.parentNode;
rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer;
rng.endOffset = nodeIndex(endContainer) + 1;
}
@ -910,7 +876,7 @@ define("tinymce/Formatter", [
ed.nodeChanged();
} else {
performCaretAction('remove', name, vars);
performCaretAction('remove', name, vars, similar);
}
}
@ -1024,6 +990,10 @@ define("tinymce/Formatter", [
function matchParents(node) {
var root = dom.getRoot();
if (node === root) {
return false;
}
// Find first node with similar format settings
node = dom.getParent(node, function(node) {
return node.parentNode === root || !!matchNode(node, name, vars, true);
@ -1138,6 +1108,11 @@ define("tinymce/Formatter", [
ed.on('NodeChange', function(e) {
var parents = getParents(e.element), matchedFormats = {};
// Ignore bogus nodes like the <a> tag created by moveStart()
parents = Tools.grep(parents, function(node) {
return node.nodeType == 1 && !node.getAttribute('data-mce-bogus');
});
// Check for new formats
each(formatChangeData, function(callbacks, format) {
each(parents, function(node) {
@ -1183,10 +1158,25 @@ define("tinymce/Formatter", [
return this;
}
/**
* Returns a preview css text for the specified format.
*
* @method getCssText
* @param {String/Object} format Format to generate preview css text for.
* @return {String} Css text for the specified format.
* @example
* var cssText1 = editor.formatter.getCssText('bold');
* var cssText2 = editor.formatter.getCssText({inline: 'b'});
*/
function getCssText(format) {
return Preview.getCssText(ed, format);
}
// Expose to public
extend(this, {
get: get,
register: register,
unregister: unregister,
apply: apply,
remove: remove,
toggle: toggle,
@ -1194,14 +1184,15 @@ define("tinymce/Formatter", [
matchAll: matchAll,
matchNode: matchNode,
canApply: canApply,
formatChanged: formatChanged
formatChanged: formatChanged,
getCssText: getCssText
});
// Initialize
defaultFormats();
addKeyboardShortcuts();
ed.on('BeforeGetContent', function() {
if (markCaretContainersBogus) {
ed.on('BeforeGetContent', function(e) {
if (markCaretContainersBogus && e.format != 'raw') {
markCaretContainersBogus();
}
});
@ -1306,7 +1297,7 @@ define("tinymce/Formatter", [
* @return {String} New value with replaced variables.
*/
function replaceVars(value, vars) {
if (typeof(value) != "string") {
if (typeof value != "string") {
value = value(vars);
} else if (vars) {
value = value.replace(/%(\w+)/g, function(str, name) {
@ -1367,7 +1358,8 @@ define("tinymce/Formatter", [
}
}
for (;;) {
/*eslint no-constant-condition:0 */
while (true) {
// Stop expanding on block elements
if (!format[0].block_expand && isBlock(parent)) {
return parent;
@ -1405,7 +1397,7 @@ define("tinymce/Formatter", [
offset = node.nodeType === 3 ? node.length : node.childNodes.length;
}
}
return { node: node, offset: offset };
return {node: node, offset: offset};
}
// If index based start position then resolve it
@ -1449,7 +1441,7 @@ define("tinymce/Formatter", [
function findSpace(node, offset) {
var pos, pos2, str = node.nodeValue;
if (typeof(offset) == "undefined") {
if (typeof offset == "undefined") {
offset = start ? str.length : 0;
}
@ -1538,7 +1530,7 @@ define("tinymce/Formatter", [
// Expand to block of similar type
if (!format[0].wrapper) {
node = dom.getParent(container, format[0].block);
node = dom.getParent(container, format[0].block, root);
}
// Expand to first wrappable block element or any block element
@ -1690,6 +1682,10 @@ define("tinymce/Formatter", [
};
}
function isColorFormatAndAnchor(node, format) {
return format.links && node.tagName == 'A';
}
/**
* Removes the specified format for the specified node. It will also remove the node if it doesn't have
* any attributes if the format specifies it to do so.
@ -1705,7 +1701,7 @@ define("tinymce/Formatter", [
var i, attrs, stylesModified;
// Check if node matches format
if (!matchName(node, format)) {
if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) {
return FALSE;
}
@ -1716,12 +1712,12 @@ define("tinymce/Formatter", [
value = normalizeStyleValue(replaceVars(value, vars), name);
// Indexed array
if (typeof(name) === 'number') {
if (typeof name === 'number') {
name = value;
compare_node = 0;
}
if (!compare_node || isEq(getStyle(compare_node, name), value)) {
if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
dom.setStyle(node, name, '');
}
@ -1741,7 +1737,7 @@ define("tinymce/Formatter", [
value = replaceVars(value, vars);
// Indexed array
if (typeof(name) === 'number') {
if (typeof name === 'number') {
name = value;
compare_node = 0;
}
@ -1754,7 +1750,7 @@ define("tinymce/Formatter", [
// Build new class value where everything is removed except the internal prefixed classes
valueOut = '';
each(value.split(/\s+/), function(cls) {
if (/mce\w+/.test(cls)) {
if (/mce\-\w+/.test(cls)) {
valueOut += (valueOut ? ' ' : '') + cls;
}
});
@ -1895,17 +1891,6 @@ define("tinymce/Formatter", [
}
}
/**
* Checks if the specified node is a bookmark node or not.
*
* @private
* @param {Node} node Node to check if it's a bookmark node or not.
* @return {Boolean} true/false if the node is a bookmark node.
*/
function isBookmarkNode(node) {
return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
}
/**
* Merges the next/previous sibling element if they match.
*
@ -1915,99 +1900,7 @@ define("tinymce/Formatter", [
* @return {Node} Next node if we didn't merge and prev node if we did.
*/
function mergeSiblings(prev, next) {
var sibling, tmpSibling;
/**
* Compares two nodes and checks if it's attributes and styles matches.
* This doesn't compare classes as items since their order is significant.
*
* @private
* @param {Node} node1 First node to compare with.
* @param {Node} node2 Second node to compare with.
* @return {boolean} True/false if the nodes are the same or not.
*/
function compareElements(node1, node2) {
// Not the same name
if (node1.nodeName != node2.nodeName) {
return FALSE;
}
/**
* Returns all the nodes attributes excluding internal ones, styles and classes.
*
* @private
* @param {Node} node Node to get attributes from.
* @return {Object} Name/value object with attributes and attribute values.
*/
function getAttribs(node) {
var attribs = {};
each(dom.getAttribs(node), function(attr) {
var name = attr.nodeName.toLowerCase();
// Don't compare internal attributes or style
if (name.indexOf('_') !== 0 && name !== 'style') {
attribs[name] = dom.getAttrib(node, name);
}
});
return attribs;
}
/**
* Compares two objects checks if it's key + value exists in the other one.
*
* @private
* @param {Object} obj1 First object to compare.
* @param {Object} obj2 Second object to compare.
* @return {boolean} True/false if the objects matches or not.
*/
function compareObjects(obj1, obj2) {
var value, name;
for (name in obj1) {
// Obj1 has item obj2 doesn't have
if (obj1.hasOwnProperty(name)) {
value = obj2[name];
// Obj2 doesn't have obj1 item
if (value === undef) {
return FALSE;
}
// Obj2 item has a different value
if (obj1[name] != value) {
return FALSE;
}
// Delete similar value
delete obj2[name];
}
}
// Check if obj 2 has something obj 1 doesn't have
for (name in obj2) {
// Obj2 has item obj1 doesn't have
if (obj2.hasOwnProperty(name)) {
return FALSE;
}
}
return TRUE;
}
// Attribs are not the same
if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
return FALSE;
}
// Styles are not the same
if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
return FALSE;
}
return TRUE;
}
var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
function findElementSibling(node, sibling_name) {
for (sibling = node; sibling; sibling = sibling[sibling_name]) {
@ -2030,7 +1923,7 @@ define("tinymce/Formatter", [
next = findElementSibling(next, 'nextSibling');
// Compare next and previous nodes
if (compareElements(prev, next)) {
if (elementUtils.compare(prev, next)) {
// Append nodes between
for (sibling = prev.nextSibling; sibling && sibling != next;) {
tmpSibling = sibling;
@ -2082,7 +1975,7 @@ define("tinymce/Formatter", [
return container;
}
function performCaretAction(type, name, vars) {
function performCaretAction(type, name, vars, similar) {
var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
// Creates a caret container bogus element
@ -2165,7 +2058,16 @@ define("tinymce/Formatter", [
child = findFirstTextNode(node);
if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
child = child.deleteData(0, 1);
child.deleteData(0, 1);
// Fix for bug #6976
if (rng.startContainer == child && rng.startOffset > 0) {
rng.setStart(child, rng.startOffset - 1);
}
if (rng.endContainer == child && rng.endOffset > 0) {
rng.setEnd(child, rng.endOffset - 1);
}
}
dom.remove(node, 1);
@ -2233,7 +2135,7 @@ define("tinymce/Formatter", [
node = container;
if (container.nodeType == 3) {
if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
if (offset != container.nodeValue.length) {
hasContentAfter = true;
}
@ -2241,7 +2143,7 @@ define("tinymce/Formatter", [
}
while (node) {
if (matchNode(node, name, vars)) {
if (matchNode(node, name, vars, similar)) {
formatNode = node;
break;
}
@ -2302,7 +2204,7 @@ define("tinymce/Formatter", [
// Move selection to text node
selection.setCursorLocation(node, 1);
// If the formatNode is empty, we can remove it safely.
// If the formatNode is empty, we can remove it safely.
if (dom.isEmpty(formatNode)) {
dom.remove(formatNode);
}
@ -2345,7 +2247,8 @@ define("tinymce/Formatter", [
removeCaretContainer();
// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
// Backspace key needs to check if the range is collapsed due to bug #6780
if ((keyCode == 8 && selection.isCollapsed()) || keyCode == 37 || keyCode == 39) {
removeCaretContainer(getParentCaretContainer(selection.getStart()));
}
@ -2400,7 +2303,7 @@ define("tinymce/Formatter", [
if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
// IE has a "neat" feature where it moves the start node into the closest element
// we can avoid this by inserting an element before it and then remove it after we set the selection
tmpNode = dom.create('a', null, INVISIBLE_CHAR);
tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
node.parentNode.insertBefore(tmpNode, node);
// Set selection and remove tmpNode