1163 lines
33 KiB
JavaScript
Executable File
1163 lines
33 KiB
JavaScript
Executable File
/**
|
||
* EditorCommands.js
|
||
*
|
||
* Copyright, Moxiecode Systems AB
|
||
* Released under LGPL License.
|
||
*
|
||
* License: http://www.tinymce.com/license
|
||
* Contributing: http://www.tinymce.com/contributing
|
||
*/
|
||
|
||
/**
|
||
* This class enables you to add custom editor commands and it contains
|
||
* overrides for native browser commands to address various bugs and issues.
|
||
*
|
||
* @class tinymce.EditorCommands
|
||
*/
|
||
define("tinymce/EditorCommands", [
|
||
"tinymce/html/Serializer",
|
||
"tinymce/Env",
|
||
"tinymce/util/Tools",
|
||
"tinymce/dom/ElementUtils",
|
||
"tinymce/dom/RangeUtils",
|
||
"tinymce/dom/TreeWalker"
|
||
], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
|
||
// Added for compression purposes
|
||
var each = Tools.each, extend = Tools.extend;
|
||
var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
|
||
var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
|
||
var TRUE = true, FALSE = false;
|
||
|
||
return function(editor) {
|
||
var dom, selection, formatter,
|
||
commands = {state: {}, exec: {}, value: {}},
|
||
settings = editor.settings,
|
||
bookmark;
|
||
|
||
editor.on('PreInit', function() {
|
||
dom = editor.dom;
|
||
selection = editor.selection;
|
||
settings = editor.settings;
|
||
formatter = editor.formatter;
|
||
});
|
||
|
||
/**
|
||
* Executes the specified command.
|
||
*
|
||
* @method execCommand
|
||
* @param {String} command Command to execute.
|
||
* @param {Boolean} ui Optional user interface state.
|
||
* @param {Object} value Optional value for command.
|
||
* @return {Boolean} true/false if the command was found or not.
|
||
*/
|
||
function execCommand(command, ui, value, args) {
|
||
var func, customCommand, state = 0;
|
||
|
||
if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
|
||
// life ace
|
||
if(command != "toggleCode")
|
||
editor.focus();
|
||
}
|
||
|
||
args = extend({}, args);
|
||
args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
|
||
if (args.isDefaultPrevented()) {
|
||
return false;
|
||
}
|
||
|
||
customCommand = command.toLowerCase();
|
||
if ((func = commands.exec[customCommand])) {
|
||
func(customCommand, ui, value);
|
||
editor.fire('ExecCommand', {command: command, ui: ui, value: value});
|
||
return true;
|
||
}
|
||
|
||
// Plugin commands
|
||
each(editor.plugins, function(p) {
|
||
if (p.execCommand && p.execCommand(command, ui, value)) {
|
||
editor.fire('ExecCommand', {command: command, ui: ui, value: value});
|
||
state = true;
|
||
return false;
|
||
}
|
||
});
|
||
|
||
if (state) {
|
||
return state;
|
||
}
|
||
|
||
// Theme commands
|
||
if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
|
||
editor.fire('ExecCommand', {command: command, ui: ui, value: value});
|
||
return true;
|
||
}
|
||
|
||
// Browser commands
|
||
try {
|
||
state = editor.getDoc().execCommand(command, ui, value);
|
||
} catch (ex) {
|
||
// Ignore old IE errors
|
||
}
|
||
|
||
if (state) {
|
||
editor.fire('ExecCommand', {command: command, ui: ui, value: value});
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Queries the current state for a command for example if the current selection is "bold".
|
||
*
|
||
* @method queryCommandState
|
||
* @param {String} command Command to check the state of.
|
||
* @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
|
||
*/
|
||
function queryCommandState(command) {
|
||
var func;
|
||
|
||
// Is hidden then return undefined
|
||
if (editor._isHidden()) {
|
||
return;
|
||
}
|
||
|
||
command = command.toLowerCase();
|
||
if ((func = commands.state[command])) {
|
||
return func(command);
|
||
}
|
||
|
||
// Browser commands
|
||
try {
|
||
return editor.getDoc().queryCommandState(command);
|
||
} catch (ex) {
|
||
// Fails sometimes see bug: 1896577
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Queries the command value for example the current fontsize.
|
||
*
|
||
* @method queryCommandValue
|
||
* @param {String} command Command to check the value of.
|
||
* @return {Object} Command value of false if it's not found.
|
||
*/
|
||
function queryCommandValue(command) {
|
||
var func;
|
||
|
||
// Is hidden then return undefined
|
||
if (editor._isHidden()) {
|
||
return;
|
||
}
|
||
|
||
command = command.toLowerCase();
|
||
if ((func = commands.value[command])) {
|
||
return func(command);
|
||
}
|
||
|
||
// Browser commands
|
||
try {
|
||
return editor.getDoc().queryCommandValue(command);
|
||
} catch (ex) {
|
||
// Fails sometimes see bug: 1896577
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Adds commands to the command collection.
|
||
*
|
||
* @method addCommands
|
||
* @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
|
||
* @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
|
||
*/
|
||
function addCommands(command_list, type) {
|
||
type = type || 'exec';
|
||
|
||
each(command_list, function(callback, command) {
|
||
each(command.toLowerCase().split(','), function(command) {
|
||
commands[type][command] = callback;
|
||
});
|
||
});
|
||
}
|
||
|
||
function addCommand(command, callback, scope) {
|
||
command = command.toLowerCase();
|
||
commands.exec[command] = function(command, ui, value, args) {
|
||
return callback.call(scope || editor, ui, value, args);
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Returns true/false if the command is supported or not.
|
||
*
|
||
* @method queryCommandSupported
|
||
* @param {String} cmd Command that we check support for.
|
||
* @return {Boolean} true/false if the command is supported or not.
|
||
*/
|
||
function queryCommandSupported(command) {
|
||
command = command.toLowerCase();
|
||
|
||
if (commands.exec[command]) {
|
||
return true;
|
||
}
|
||
|
||
// Browser commands
|
||
try {
|
||
return editor.getDoc().queryCommandSupported(command);
|
||
} catch (ex) {
|
||
// Fails sometimes see bug: 1896577
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
function addQueryStateHandler(command, callback, scope) {
|
||
command = command.toLowerCase();
|
||
commands.state[command] = function() {
|
||
return callback.call(scope || editor);
|
||
};
|
||
}
|
||
|
||
function addQueryValueHandler(command, callback, scope) {
|
||
command = command.toLowerCase();
|
||
commands.value[command] = function() {
|
||
return callback.call(scope || editor);
|
||
};
|
||
}
|
||
|
||
function hasCustomCommand(command) {
|
||
command = command.toLowerCase();
|
||
return !!commands.exec[command];
|
||
}
|
||
|
||
// Expose public methods
|
||
extend(this, {
|
||
execCommand: execCommand,
|
||
queryCommandState: queryCommandState,
|
||
queryCommandValue: queryCommandValue,
|
||
queryCommandSupported: queryCommandSupported,
|
||
addCommands: addCommands,
|
||
addCommand: addCommand,
|
||
addQueryStateHandler: addQueryStateHandler,
|
||
addQueryValueHandler: addQueryValueHandler,
|
||
hasCustomCommand: hasCustomCommand
|
||
});
|
||
|
||
// Private methods
|
||
|
||
function execNativeCommand(command, ui, value) {
|
||
if (ui === undefined) {
|
||
ui = FALSE;
|
||
}
|
||
|
||
if (value === undefined) {
|
||
value = null;
|
||
}
|
||
|
||
return editor.getDoc().execCommand(command, ui, value);
|
||
}
|
||
|
||
function isFormatMatch(name) {
|
||
return formatter.match(name);
|
||
}
|
||
|
||
function toggleFormat(name, value) {
|
||
formatter.toggle(name, value ? {value: value} : undefined);
|
||
editor.nodeChanged();
|
||
}
|
||
|
||
function storeSelection(type) {
|
||
bookmark = selection.getBookmark(type);
|
||
}
|
||
|
||
function restoreSelection() {
|
||
selection.moveToBookmark(bookmark);
|
||
}
|
||
|
||
// Add execCommand overrides
|
||
addCommands({
|
||
// Ignore these, added for compatibility
|
||
'mceResetDesignMode,mceBeginUndoLevel': function() {},
|
||
|
||
// Add undo manager logic
|
||
'mceEndUndoLevel,mceAddUndoLevel': function() {
|
||
editor.undoManager.add();
|
||
},
|
||
|
||
'Cut,Copy,Paste': function(command) {
|
||
var doc = editor.getDoc(), failed;
|
||
|
||
// Try executing the native command
|
||
try {
|
||
execNativeCommand(command);
|
||
} catch (ex) {
|
||
// Command failed
|
||
failed = TRUE;
|
||
}
|
||
|
||
// Present alert message about clipboard access not being available
|
||
if (failed || !doc.queryCommandSupported(command)) {
|
||
var msg = editor.translate(
|
||
"Your browser doesn't support direct access to the clipboard. " +
|
||
"Please use the Ctrl+X/C/V keyboard shortcuts instead."
|
||
);
|
||
|
||
if (Env.mac) {
|
||
msg = msg.replace(/Ctrl\+/g, '\u2318+');
|
||
}
|
||
|
||
editor.windowManager.alert(msg);
|
||
}
|
||
},
|
||
|
||
// Override unlink command
|
||
unlink: function() {
|
||
if (selection.isCollapsed()) {
|
||
var elm = selection.getNode();
|
||
if (elm.tagName == 'A') {
|
||
editor.dom.remove(elm, true);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
formatter.remove("link");
|
||
},
|
||
|
||
// Override justify commands to use the text formatter engine
|
||
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
|
||
var align = command.substring(7);
|
||
|
||
if (align == 'full') {
|
||
align = 'justify';
|
||
}
|
||
|
||
// Remove all other alignments first
|
||
each('left,center,right,justify'.split(','), function(name) {
|
||
if (align != name) {
|
||
formatter.remove('align' + name);
|
||
}
|
||
});
|
||
|
||
toggleFormat('align' + align);
|
||
execCommand('mceRepaint');
|
||
},
|
||
|
||
// Override list commands to fix WebKit bug
|
||
'InsertUnorderedList,InsertOrderedList': function(command) {
|
||
var listElm, listParent;
|
||
|
||
execNativeCommand(command);
|
||
|
||
// WebKit produces lists within block elements so we need to split them
|
||
// we will replace the native list creation logic to custom logic later on
|
||
// TODO: Remove this when the list creation logic is removed
|
||
listElm = dom.getParent(selection.getNode(), 'ol,ul');
|
||
if (listElm) {
|
||
listParent = listElm.parentNode;
|
||
|
||
// If list is within a text block then split that block
|
||
if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
|
||
storeSelection();
|
||
dom.split(listParent, listElm);
|
||
restoreSelection();
|
||
}
|
||
}
|
||
},
|
||
|
||
// Override commands to use the text formatter engine
|
||
'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
|
||
toggleFormat(command);
|
||
},
|
||
|
||
// Override commands to use the text formatter engine
|
||
'ForeColor,HiliteColor,FontName': function(command, ui, value) {
|
||
toggleFormat(command, value);
|
||
},
|
||
|
||
FontSize: function(command, ui, value) {
|
||
var fontClasses, fontSizes;
|
||
|
||
// Convert font size 1-7 to styles
|
||
if (value >= 1 && value <= 7) {
|
||
fontSizes = explode(settings.font_size_style_values);
|
||
fontClasses = explode(settings.font_size_classes);
|
||
|
||
if (fontClasses) {
|
||
value = fontClasses[value - 1] || value;
|
||
} else {
|
||
value = fontSizes[value - 1] || value;
|
||
}
|
||
}
|
||
|
||
toggleFormat(command, value);
|
||
},
|
||
|
||
RemoveFormat: function(command) {
|
||
formatter.remove(command);
|
||
},
|
||
|
||
mceBlockQuote: function() {
|
||
toggleFormat('blockquote');
|
||
},
|
||
|
||
FormatBlock: function(command, ui, value) {
|
||
return toggleFormat(value || 'p');
|
||
},
|
||
|
||
mceCleanup: function() {
|
||
var bookmark = selection.getBookmark();
|
||
|
||
editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
|
||
|
||
selection.moveToBookmark(bookmark);
|
||
},
|
||
|
||
mceRemoveNode: function(command, ui, value) {
|
||
var node = value || selection.getNode();
|
||
|
||
// Make sure that the body node isn't removed
|
||
if (node != editor.getBody()) {
|
||
storeSelection();
|
||
editor.dom.remove(node, TRUE);
|
||
restoreSelection();
|
||
}
|
||
},
|
||
|
||
mceSelectNodeDepth: function(command, ui, value) {
|
||
var counter = 0;
|
||
|
||
dom.getParent(selection.getNode(), function(node) {
|
||
if (node.nodeType == 1 && counter++ == value) {
|
||
selection.select(node);
|
||
return FALSE;
|
||
}
|
||
}, editor.getBody());
|
||
},
|
||
|
||
mceSelectNode: function(command, ui, value) {
|
||
selection.select(value);
|
||
},
|
||
|
||
mceInsertContent: function(command, ui, value) {
|
||
var parser, serializer, parentNode, rootNode, fragment, args;
|
||
var marker, rng, node, node2, bookmarkHtml, merge;
|
||
var textInlineElements = editor.schema.getTextInlineElements();
|
||
|
||
function trimOrPaddLeftRight(html) {
|
||
var rng, container, offset;
|
||
|
||
rng = selection.getRng(true);
|
||
container = rng.startContainer;
|
||
offset = rng.startOffset;
|
||
|
||
function hasSiblingText(siblingName) {
|
||
return container[siblingName] && container[siblingName].nodeType == 3;
|
||
}
|
||
|
||
if (container.nodeType == 3) {
|
||
if (offset > 0) {
|
||
html = html.replace(/^ /, ' ');
|
||
} else if (!hasSiblingText('previousSibling')) {
|
||
html = html.replace(/^ /, ' ');
|
||
}
|
||
|
||
if (offset < container.length) {
|
||
html = html.replace(/ (<br>|)$/, ' ');
|
||
} else if (!hasSiblingText('nextSibling')) {
|
||
html = html.replace(/( | )(<br>|)$/, ' ');
|
||
}
|
||
}
|
||
|
||
return html;
|
||
}
|
||
|
||
// Removes from a [b] c -> a c -> a c
|
||
function trimNbspAfterDeleteAndPaddValue() {
|
||
var rng, container, offset;
|
||
|
||
rng = selection.getRng(true);
|
||
container = rng.startContainer;
|
||
offset = rng.startOffset;
|
||
|
||
if (container.nodeType == 3 && rng.collapsed) {
|
||
if (container.data[offset] === '\u00a0') {
|
||
container.deleteData(offset, 1);
|
||
|
||
if (!/[\u00a0| ]$/.test(value)) {
|
||
value += ' ';
|
||
}
|
||
} else if (container.data[offset - 1] === '\u00a0') {
|
||
container.deleteData(offset - 1, 1);
|
||
|
||
if (!/[\u00a0| ]$/.test(value)) {
|
||
value = ' ' + value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function markInlineFormatElements(fragment) {
|
||
if (merge) {
|
||
for (node = fragment.firstChild; node; node = node.walk(true)) {
|
||
if (textInlineElements[node.name]) {
|
||
node.attr('data-mce-new', "true");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function reduceInlineTextElements() {
|
||
if (merge) {
|
||
var root = editor.getBody(), elementUtils = new ElementUtils(dom);
|
||
|
||
each(dom.select('*[data-mce-new]'), function(node) {
|
||
node.removeAttribute('data-mce-new');
|
||
|
||
for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
|
||
if (elementUtils.compare(testNode, node)) {
|
||
dom.remove(node, true);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
if (typeof value != 'string') {
|
||
merge = value.merge;
|
||
value = value.content;
|
||
}
|
||
|
||
// Check for whitespace before/after value
|
||
if (/^ | $/.test(value)) {
|
||
value = trimOrPaddLeftRight(value);
|
||
}
|
||
|
||
// Setup parser and serializer
|
||
parser = editor.parser;
|
||
serializer = new Serializer({}, editor.schema);
|
||
bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">​</span>';
|
||
|
||
// Run beforeSetContent handlers on the HTML to be inserted
|
||
args = {content: value, format: 'html', selection: true};
|
||
editor.fire('BeforeSetContent', args);
|
||
value = args.content;
|
||
|
||
// Add caret at end of contents if it's missing
|
||
if (value.indexOf('{$caret}') == -1) {
|
||
value += '{$caret}';
|
||
}
|
||
|
||
// Replace the caret marker with a span bookmark element
|
||
value = value.replace(/\{\$caret\}/, bookmarkHtml);
|
||
|
||
// If selection is at <body>|<p></p> then move it into <body><p>|</p>
|
||
rng = selection.getRng();
|
||
var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
|
||
var body = editor.getBody();
|
||
if (caretElement === body && selection.isCollapsed()) {
|
||
if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
|
||
rng = dom.createRng();
|
||
rng.setStart(body.firstChild, 0);
|
||
rng.setEnd(body.firstChild, 0);
|
||
selection.setRng(rng);
|
||
}
|
||
}
|
||
|
||
// Insert node maker where we will insert the new HTML and get it's parent
|
||
if (!selection.isCollapsed()) {
|
||
editor.getDoc().execCommand('Delete', false, null);
|
||
trimNbspAfterDeleteAndPaddValue();
|
||
}
|
||
|
||
parentNode = selection.getNode();
|
||
|
||
// Parse the fragment within the context of the parent node
|
||
var parserArgs = {context: parentNode.nodeName.toLowerCase()};
|
||
fragment = parser.parse(value, parserArgs);
|
||
|
||
markInlineFormatElements(fragment);
|
||
|
||
// Move the caret to a more suitable location
|
||
node = fragment.lastChild;
|
||
if (node.attr('id') == 'mce_marker') {
|
||
marker = node;
|
||
|
||
for (node = node.prev; node; node = node.walk(true)) {
|
||
if (node.type == 3 || !dom.isBlock(node.name)) {
|
||
if (editor.schema.isValidChild(node.parent.name, 'span')) {
|
||
node.parent.insert(marker, node, node.name === 'br');
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// If parser says valid we can insert the contents into that parent
|
||
if (!parserArgs.invalid) {
|
||
value = serializer.serialize(fragment);
|
||
|
||
// Check if parent is empty or only has one BR element then set the innerHTML of that parent
|
||
node = parentNode.firstChild;
|
||
node2 = parentNode.lastChild;
|
||
if (!node || (node === node2 && node.nodeName === 'BR')) {
|
||
dom.setHTML(parentNode, value);
|
||
} else {
|
||
selection.setContent(value);
|
||
}
|
||
} else {
|
||
// If the fragment was invalid within that context then we need
|
||
// to parse and process the parent it's inserted into
|
||
|
||
// Insert bookmark node and get the parent
|
||
selection.setContent(bookmarkHtml);
|
||
parentNode = selection.getNode();
|
||
rootNode = editor.getBody();
|
||
|
||
// Opera will return the document node when selection is in root
|
||
if (parentNode.nodeType == 9) {
|
||
parentNode = node = rootNode;
|
||
} else {
|
||
node = parentNode;
|
||
}
|
||
|
||
// Find the ancestor just before the root element
|
||
while (node !== rootNode) {
|
||
parentNode = node;
|
||
node = node.parentNode;
|
||
}
|
||
|
||
// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
|
||
value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
|
||
value = serializer.serialize(
|
||
parser.parse(
|
||
// Need to replace by using a function since $ in the contents would otherwise be a problem
|
||
value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
|
||
return serializer.serialize(fragment);
|
||
})
|
||
)
|
||
);
|
||
|
||
// life ace
|
||
// 插入的时候把<pre>内的标签全清掉
|
||
// life 把<pre></pre>间的代码拿出, 去掉标签<span>之类的
|
||
// console.log(value);
|
||
// <p> </p><pre>xxx<span id="mce_marker" data-mce-type="bookmark"></span></pre>
|
||
// <p> </p><pre >xxx</pre>
|
||
// console.log('life');
|
||
// console.log(value);
|
||
value = value.replace(/<pre([^>]*?)>([\s\S]*?)<\/pre>/g, function(v, v1, v2) {
|
||
// v == "<pre>a, b, c</pre>"
|
||
var hasBookmark = false;
|
||
var b = '<span id="mce_marker" data-mce-type="bookmark"></span>';
|
||
if(v2.indexOf('id="mce_marker') != -1) { // mini后不是b了
|
||
hasBookmark = true;
|
||
}
|
||
|
||
v2 = v2.replace(/(<([^>]+)>)/gi, '').replace(/\s+$/, ''); // 把最后一个换行去掉
|
||
if(hasBookmark) {
|
||
v2 += b;
|
||
}
|
||
return "<pre " + v1 + ">" + v2 + "</pre>";
|
||
});
|
||
// console.log(value);
|
||
|
||
// Set the inner/outer HTML depending on if we are in the root or not
|
||
if (parentNode == rootNode) {
|
||
dom.setHTML(rootNode, value);
|
||
} else {
|
||
dom.setOuterHTML(parentNode, value);
|
||
}
|
||
}
|
||
|
||
reduceInlineTextElements();
|
||
|
||
marker = dom.get('mce_marker');
|
||
selection.scrollIntoView(marker);
|
||
|
||
// Move selection before marker and remove it
|
||
rng = dom.createRng();
|
||
|
||
// If previous sibling is a text node set the selection to the end of that node
|
||
node = marker.previousSibling;
|
||
if (node && node.nodeType == 3) {
|
||
rng.setStart(node, node.nodeValue.length);
|
||
|
||
// TODO: Why can't we normalize on IE
|
||
if (!isIE) {
|
||
node2 = marker.nextSibling;
|
||
if (node2 && node2.nodeType == 3) {
|
||
node.appendData(node2.data);
|
||
node2.parentNode.removeChild(node2);
|
||
}
|
||
}
|
||
} else {
|
||
// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
|
||
rng.setStartBefore(marker);
|
||
rng.setEndBefore(marker);
|
||
}
|
||
|
||
// Remove the marker node and set the new range
|
||
dom.remove(marker);
|
||
selection.setRng(rng);
|
||
|
||
// Dispatch after event and add any visual elements needed
|
||
editor.fire('SetContent', args);
|
||
editor.addVisual();
|
||
},
|
||
|
||
// life ace
|
||
// life 修改
|
||
// 之前不是这个版本, 为什么要改, 因为考虑到在pre下粘贴后全部内容会修改(ace不友好)
|
||
// 这里不是改全部的内容, 对ace好
|
||
mceInsertRawHTML: function(command, ui, value) {
|
||
var parser, serializer, parentNode, rootNode, fragment, args;
|
||
var marker, rng, node, node2, bookmarkHtml;
|
||
|
||
// Setup parser and serializer
|
||
parser = editor.parser;
|
||
serializer = new Serializer({}, editor.schema);
|
||
bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark"></span>';
|
||
|
||
// Run beforeSetContent handlers on the HTML to be inserted
|
||
args = {content: value, format: 'html', selection: true};
|
||
editor.fire('BeforeSetContent', args);
|
||
value = args.content;
|
||
|
||
// Add caret at end of contents if it's missing
|
||
if (value.indexOf('{$caret}') == -1) {
|
||
value += '{$caret}';
|
||
}
|
||
|
||
// Replace the caret marker with a span bookmark element
|
||
value = value.replace(/\{\$caret\}/, bookmarkHtml);
|
||
|
||
// If selection is at <body>|<p></p> then move it into <body><p>|</p>
|
||
var body = editor.getBody();
|
||
if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
|
||
body.firstChild.appendChild(dom.doc.createTextNode('\u00a0'));
|
||
selection.select(body.firstChild, true);
|
||
dom.remove(body.firstChild.lastChild);
|
||
}
|
||
|
||
// Insert node maker where we will insert the new HTML and get it's parent
|
||
if (!selection.isCollapsed()) {
|
||
editor.getDoc().execCommand('Delete', false, null);
|
||
}
|
||
|
||
parentNode = selection.getNode();
|
||
|
||
// Parse the fragment within the context of the parent node
|
||
var parserArgs = {context: parentNode.nodeName.toLowerCase()};
|
||
fragment = parser.parse(value, parserArgs);
|
||
|
||
// Move the caret to a more suitable location
|
||
node = fragment.lastChild;
|
||
if (node.attr('id') == 'mce_marker') {
|
||
marker = node;
|
||
|
||
for (node = node.prev; node; node = node.walk(true)) {
|
||
if (node.type == 3 || !dom.isBlock(node.name)) {
|
||
node.parent.insert(marker, node, node.name === 'br');
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// If parser says valid we can insert the contents into that parent
|
||
if (!parserArgs.invalid) {
|
||
// Check if parent is empty or only has one BR element then set the innerHTML of that parent
|
||
node = parentNode.firstChild;
|
||
node2 = parentNode.lastChild;
|
||
if (!node || (node === node2 && node.nodeName === 'BR')) {
|
||
dom.setHTML(parentNode, value);
|
||
} else {
|
||
selection.setContent(value);
|
||
}
|
||
} else {
|
||
// If the fragment was invalid within that context then we need
|
||
// to parse and process the parent it's inserted into
|
||
|
||
// Insert bookmark node and get the parent
|
||
selection.setContent(bookmarkHtml);
|
||
parentNode = selection.getNode();
|
||
rootNode = editor.getBody();
|
||
|
||
// Opera will return the document node when selection is in root
|
||
if (parentNode.nodeType == 9) {
|
||
parentNode = node = rootNode;
|
||
} else {
|
||
node = parentNode;
|
||
}
|
||
|
||
// Find the ancestor just before the root element
|
||
while (node !== rootNode) {
|
||
parentNode = node;
|
||
node = node.parentNode;
|
||
}
|
||
|
||
// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
|
||
value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
|
||
|
||
// Set the inner/outer HTML depending on if we are in the root or not
|
||
if (parentNode == rootNode) {
|
||
dom.setHTML(rootNode, value);
|
||
} else {
|
||
dom.setOuterHTML(parentNode, value);
|
||
}
|
||
}
|
||
|
||
marker = dom.get('mce_marker');
|
||
selection.scrollIntoView(marker);
|
||
|
||
// Move selection before marker and remove it
|
||
rng = dom.createRng();
|
||
|
||
// If previous sibling is a text node set the selection to the end of that node
|
||
node = marker.previousSibling;
|
||
if (node && node.nodeType == 3) {
|
||
rng.setStart(node, node.nodeValue.length);
|
||
|
||
// TODO: Why can't we normalize on IE
|
||
if (!isIE) {
|
||
node2 = marker.nextSibling;
|
||
if (node2 && node2.nodeType == 3) {
|
||
node.appendData(node2.data);
|
||
node2.parentNode.removeChild(node2);
|
||
}
|
||
}
|
||
} else {
|
||
// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
|
||
rng.setStartBefore(marker);
|
||
rng.setEndBefore(marker);
|
||
}
|
||
|
||
// Remove the marker node and set the new range
|
||
dom.remove(marker);
|
||
selection.setRng(rng);
|
||
|
||
// Dispatch after event and add any visual elements needed
|
||
editor.fire('SetContent', args);
|
||
editor.addVisual()
|
||
},
|
||
|
||
/* life
|
||
mceInsertRawHTML: function(command, ui, value) {
|
||
selection.setContent('tiny_mce_marker');
|
||
editor.setContent(
|
||
editor.getContent().replace(/tiny_mce_marker/g, function() {
|
||
return value;
|
||
})
|
||
);
|
||
},
|
||
*/
|
||
|
||
mceToggleFormat: function(command, ui, value) {
|
||
toggleFormat(value);
|
||
},
|
||
|
||
mceSetContent: function(command, ui, value) {
|
||
editor.setContent(value);
|
||
},
|
||
|
||
'Indent,Outdent': function(command) {
|
||
var intentValue, indentUnit, value;
|
||
|
||
// Setup indent level
|
||
intentValue = settings.indentation;
|
||
indentUnit = /[a-z%]+$/i.exec(intentValue);
|
||
intentValue = parseInt(intentValue, 10);
|
||
|
||
if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
|
||
// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
|
||
if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
|
||
formatter.apply('div');
|
||
}
|
||
|
||
each(selection.getSelectedBlocks(), function(element) {
|
||
if (element.nodeName != "LI") {
|
||
var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
|
||
|
||
indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
|
||
|
||
if (command == 'outdent') {
|
||
value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
|
||
dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
|
||
} else {
|
||
value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
|
||
dom.setStyle(element, indentStyleName, value);
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
execNativeCommand(command);
|
||
}
|
||
},
|
||
|
||
mceRepaint: function() {
|
||
if (isGecko) {
|
||
try {
|
||
storeSelection(TRUE);
|
||
|
||
if (selection.getSel()) {
|
||
selection.getSel().selectAllChildren(editor.getBody());
|
||
}
|
||
|
||
selection.collapse(TRUE);
|
||
restoreSelection();
|
||
} catch (ex) {
|
||
// Ignore
|
||
}
|
||
}
|
||
},
|
||
|
||
InsertHorizontalRule: function() {
|
||
editor.execCommand('mceInsertContent', false, '<hr />');
|
||
},
|
||
|
||
mceToggleVisualAid: function() {
|
||
editor.hasVisual = !editor.hasVisual;
|
||
editor.addVisual();
|
||
},
|
||
|
||
mceReplaceContent: function(command, ui, value) {
|
||
editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
|
||
},
|
||
|
||
mceInsertLink: function(command, ui, value) {
|
||
var anchor;
|
||
|
||
if (typeof value == 'string') {
|
||
value = {href: value};
|
||
}
|
||
|
||
anchor = dom.getParent(selection.getNode(), 'a');
|
||
|
||
// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
|
||
value.href = value.href.replace(' ', '%20');
|
||
|
||
// Remove existing links if there could be child links or that the href isn't specified
|
||
if (!anchor || !value.href) {
|
||
formatter.remove('link');
|
||
}
|
||
|
||
// Apply new link to selection
|
||
if (value.href) {
|
||
formatter.apply('link', value, anchor);
|
||
}
|
||
},
|
||
|
||
selectAll: function() {
|
||
var root = dom.getRoot(), rng;
|
||
|
||
if (selection.getRng().setStart) {
|
||
rng = dom.createRng();
|
||
rng.setStart(root, 0);
|
||
rng.setEnd(root, root.childNodes.length);
|
||
selection.setRng(rng);
|
||
} else {
|
||
// IE will render it's own root level block elements and sometimes
|
||
// even put font elements in them when the user starts typing. So we need to
|
||
// move the selection to a more suitable element from this:
|
||
// <body>|<p></p></body> to this: <body><p>|</p></body>
|
||
rng = selection.getRng();
|
||
if (!rng.item) {
|
||
rng.moveToElementText(root);
|
||
rng.select();
|
||
}
|
||
}
|
||
},
|
||
|
||
"delete": function() {
|
||
execNativeCommand("Delete");
|
||
|
||
// Check if body is empty after the delete call if so then set the contents
|
||
// to an empty string and move the caret to any block produced by that operation
|
||
// this fixes the issue with root blocks not being properly produced after a delete call on IE
|
||
var body = editor.getBody();
|
||
|
||
if (dom.isEmpty(body)) {
|
||
editor.setContent('');
|
||
|
||
if (body.firstChild && dom.isBlock(body.firstChild)) {
|
||
editor.selection.setCursorLocation(body.firstChild, 0);
|
||
} else {
|
||
editor.selection.setCursorLocation(body, 0);
|
||
}
|
||
}
|
||
},
|
||
|
||
mceNewDocument: function() {
|
||
editor.setContent('');
|
||
},
|
||
|
||
InsertLineBreak: function(command, ui, value) {
|
||
// We load the current event in from EnterKey.js when appropriate to heed
|
||
// certain event-specific variations such as ctrl-enter in a list
|
||
var evt = value;
|
||
var brElm, extraBr, marker;
|
||
var rng = selection.getRng(true);
|
||
new RangeUtils(dom).normalize(rng);
|
||
|
||
var offset = rng.startOffset;
|
||
var container = rng.startContainer;
|
||
|
||
// Resolve node index
|
||
if (container.nodeType == 1 && container.hasChildNodes()) {
|
||
var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
|
||
|
||
container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
|
||
if (isAfterLastNodeInContainer && container.nodeType == 3) {
|
||
offset = container.nodeValue.length;
|
||
} else {
|
||
offset = 0;
|
||
}
|
||
}
|
||
|
||
var parentBlock = dom.getParent(container, dom.isBlock);
|
||
var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
|
||
var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
|
||
var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
|
||
|
||
// Enter inside block contained within a LI then split or insert before/after LI
|
||
var isControlKey = evt && evt.ctrlKey;
|
||
if (containerBlockName == 'LI' && !isControlKey) {
|
||
parentBlock = containerBlock;
|
||
parentBlockName = containerBlockName;
|
||
}
|
||
|
||
// Walks the parent block to the right and look for BR elements
|
||
function hasRightSideContent() {
|
||
var walker = new TreeWalker(container, parentBlock), node;
|
||
var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
|
||
|
||
while ((node = walker.next())) {
|
||
if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
|
||
// Insert extra BR element at the end block elements
|
||
if (!isOldIE && !hasRightSideContent()) {
|
||
brElm = dom.create('br');
|
||
rng.insertNode(brElm);
|
||
rng.setStartAfter(brElm);
|
||
rng.setEndAfter(brElm);
|
||
extraBr = true;
|
||
}
|
||
}
|
||
|
||
brElm = dom.create('br');
|
||
rng.insertNode(brElm);
|
||
|
||
// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
|
||
var documentMode = dom.doc.documentMode;
|
||
if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
|
||
brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
|
||
}
|
||
|
||
// Insert temp marker and scroll to that
|
||
marker = dom.create('span', {}, ' ');
|
||
brElm.parentNode.insertBefore(marker, brElm);
|
||
selection.scrollIntoView(marker);
|
||
dom.remove(marker);
|
||
|
||
if (!extraBr) {
|
||
rng.setStartAfter(brElm);
|
||
rng.setEndAfter(brElm);
|
||
} else {
|
||
rng.setStartBefore(brElm);
|
||
rng.setEndBefore(brElm);
|
||
}
|
||
|
||
selection.setRng(rng);
|
||
editor.undoManager.add();
|
||
|
||
return TRUE;
|
||
}
|
||
});
|
||
|
||
// Add queryCommandState overrides
|
||
addCommands({
|
||
// Override justify commands
|
||
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
|
||
var name = 'align' + command.substring(7);
|
||
var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
|
||
var matches = map(nodes, function(node) {
|
||
return !!formatter.matchNode(node, name);
|
||
});
|
||
return inArray(matches, TRUE) !== -1;
|
||
},
|
||
|
||
'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
|
||
return isFormatMatch(command);
|
||
},
|
||
|
||
mceBlockQuote: function() {
|
||
return isFormatMatch('blockquote');
|
||
},
|
||
|
||
Outdent: function() {
|
||
var node;
|
||
|
||
if (settings.inline_styles) {
|
||
if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
|
||
return TRUE;
|
||
}
|
||
|
||
if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
return (
|
||
queryCommandState('InsertUnorderedList') ||
|
||
queryCommandState('InsertOrderedList') ||
|
||
(!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
|
||
);
|
||
},
|
||
|
||
'InsertUnorderedList,InsertOrderedList': function(command) {
|
||
var list = dom.getParent(selection.getNode(), 'ul,ol');
|
||
|
||
return list &&
|
||
(
|
||
command === 'insertunorderedlist' && list.tagName === 'UL' ||
|
||
command === 'insertorderedlist' && list.tagName === 'OL'
|
||
);
|
||
}
|
||
}, 'state');
|
||
|
||
// Add queryCommandValue overrides
|
||
addCommands({
|
||
'FontSize,FontName': function(command) {
|
||
var value = 0, parent;
|
||
|
||
if ((parent = dom.getParent(selection.getNode(), 'span'))) {
|
||
if (command == 'fontsize') {
|
||
value = parent.style.fontSize;
|
||
} else {
|
||
value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
|
||
}
|
||
}
|
||
|
||
return value;
|
||
}
|
||
}, 'value');
|
||
|
||
// Add undo manager logic
|
||
addCommands({
|
||
Undo: function() {
|
||
editor.undoManager.undo();
|
||
},
|
||
|
||
Redo: function() {
|
||
editor.undoManager.redo();
|
||
}
|
||
});
|
||
};
|
||
});
|