v1.0
只读模式 group, 分享 评论更多问题 博客标签总是存在一个
This commit is contained in:
1 parent
7e458bb433
commit
6987a38820
1453 files changed
+114558
-91533
No files matched your search
Regular → Executable
+36
-22
@@ -58,10 +58,24 @@ define("tinymce/AddOnManager", [
|
||||
*
|
||||
* @method requireLangPack
|
||||
* @param {String} name Short name of the add-on.
|
||||
* @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
|
||||
*/
|
||||
requireLangPack: function(name) {
|
||||
if (AddOnManager.language && AddOnManager.languageLoad !== false) {
|
||||
ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + AddOnManager.language + '.js');
|
||||
requireLangPack: function(name, languages) {
|
||||
var language = AddOnManager.language;
|
||||
|
||||
if (language && AddOnManager.languageLoad !== false) {
|
||||
if (languages) {
|
||||
languages = ',' + languages + ',';
|
||||
|
||||
// Load short form sv.js or long form sv_SE.js
|
||||
if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
|
||||
language = language.substr(0, 2);
|
||||
} else if (languages.indexOf(',' + language + ',') == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -127,10 +141,10 @@ define("tinymce/AddOnManager", [
|
||||
* Loads an add-on from a specific url.
|
||||
*
|
||||
* @method load
|
||||
* @param {String} n Short name of the add-on that gets loaded.
|
||||
* @param {String} u URL to the add-on that will get loaded.
|
||||
* @param {function} cb Optional callback to execute ones the add-on is loaded.
|
||||
* @param {Object} s Optional scope to execute the callback in.
|
||||
* @param {String} name Short name of the add-on that gets loaded.
|
||||
* @param {String} addOnUrl URL to the add-on that will get loaded.
|
||||
* @param {function} callback Optional callback to execute ones the add-on is loaded.
|
||||
* @param {Object} scope Optional scope to execute the callback in.
|
||||
* @example
|
||||
* // Loads a plugin from an external URL
|
||||
* tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
|
||||
@@ -141,45 +155,45 @@ define("tinymce/AddOnManager", [
|
||||
* plugins: '-myplugin' // Don't try to load it again
|
||||
* });
|
||||
*/
|
||||
load: function(n, u, cb, s) {
|
||||
var t = this, url = u;
|
||||
load: function(name, addOnUrl, callback, scope) {
|
||||
var self = this, url = addOnUrl;
|
||||
|
||||
function loadDependencies() {
|
||||
var dependencies = t.dependencies(n);
|
||||
var dependencies = self.dependencies(name);
|
||||
|
||||
each(dependencies, function(dep) {
|
||||
var newUrl = t.createUrl(u, dep);
|
||||
var newUrl = self.createUrl(addOnUrl, dep);
|
||||
|
||||
t.load(newUrl.resource, newUrl, undefined, undefined);
|
||||
self.load(newUrl.resource, newUrl, undefined, undefined);
|
||||
});
|
||||
|
||||
if (cb) {
|
||||
if (s) {
|
||||
cb.call(s);
|
||||
if (callback) {
|
||||
if (scope) {
|
||||
callback.call(scope);
|
||||
} else {
|
||||
cb.call(ScriptLoader);
|
||||
callback.call(ScriptLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (t.urls[n]) {
|
||||
if (self.urls[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof u === "object") {
|
||||
url = u.prefix + u.resource + u.suffix;
|
||||
if (typeof addOnUrl === "object") {
|
||||
url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
|
||||
}
|
||||
|
||||
if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
|
||||
url = AddOnManager.baseURL + '/' + url;
|
||||
}
|
||||
|
||||
t.urls[n] = url.substring(0, url.lastIndexOf('/'));
|
||||
self.urls[name] = url.substring(0, url.lastIndexOf('/'));
|
||||
|
||||
if (t.lookup[n]) {
|
||||
if (self.lookup[name]) {
|
||||
loadDependencies();
|
||||
} else {
|
||||
ScriptLoader.ScriptLoader.add(url, loadDependencies, s);
|
||||
ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+255
-366
File diff suppressed because it is too large.
Load diff
Regular → Executable
+406
-65
@@ -17,22 +17,30 @@
|
||||
define("tinymce/EditorCommands", [
|
||||
"tinymce/html/Serializer",
|
||||
"tinymce/Env",
|
||||
"tinymce/util/Tools"
|
||||
], function(Serializer, Env, Tools) {
|
||||
"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;
|
||||
var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
|
||||
var TRUE = true, FALSE = false;
|
||||
|
||||
return function(editor) {
|
||||
var dom = editor.dom,
|
||||
selection = editor.selection,
|
||||
var dom, selection, formatter,
|
||||
commands = {state: {}, exec: {}, value: {}},
|
||||
settings = editor.settings,
|
||||
formatter = editor.formatter,
|
||||
bookmark;
|
||||
|
||||
editor.on('PreInit', function() {
|
||||
dom = editor.dom;
|
||||
selection = editor.selection;
|
||||
settings = editor.settings;
|
||||
formatter = editor.formatter;
|
||||
});
|
||||
|
||||
/**
|
||||
* Executes the specified command.
|
||||
*
|
||||
@@ -42,16 +50,60 @@ define("tinymce/EditorCommands", [
|
||||
* @param {Object} value Optional value for command.
|
||||
* @return {Boolean} true/false if the command was found or not.
|
||||
*/
|
||||
function execCommand(command, ui, value) {
|
||||
var func;
|
||||
function execCommand(command, ui, value, args) {
|
||||
var func, customCommand, state = 0;
|
||||
|
||||
command = command.toLowerCase();
|
||||
if ((func = commands.exec[command])) {
|
||||
func(command, ui, value);
|
||||
return TRUE;
|
||||
if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
|
||||
// life ace
|
||||
if(command != "toggleCode")
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,12 +116,24 @@ define("tinymce/EditorCommands", [
|
||||
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);
|
||||
}
|
||||
|
||||
return -1;
|
||||
// Browser commands
|
||||
try {
|
||||
return editor.getDoc().queryCommandState(command);
|
||||
} catch (ex) {
|
||||
// Fails sometimes see bug: 1896577
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,12 +146,22 @@ define("tinymce/EditorCommands", [
|
||||
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);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
// Browser commands
|
||||
try {
|
||||
return editor.getDoc().queryCommandValue(command);
|
||||
} catch (ex) {
|
||||
// Fails sometimes see bug: 1896577
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,12 +181,67 @@ define("tinymce/EditorCommands", [
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
addCommands: addCommands
|
||||
queryCommandSupported: queryCommandSupported,
|
||||
addCommands: addCommands,
|
||||
addCommand: addCommand,
|
||||
addQueryStateHandler: addQueryStateHandler,
|
||||
addQueryValueHandler: addQueryValueHandler,
|
||||
hasCustomCommand: hasCustomCommand
|
||||
});
|
||||
|
||||
// Private methods
|
||||
@@ -169,21 +298,31 @@ define("tinymce/EditorCommands", [
|
||||
|
||||
// Present alert message about clipboard access not being available
|
||||
if (failed || !doc.queryCommandSupported(command)) {
|
||||
editor.windowManager.alert(
|
||||
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(command) {
|
||||
unlink: function() {
|
||||
if (selection.isCollapsed()) {
|
||||
selection.select(selection.getNode());
|
||||
var elm = selection.getNode();
|
||||
if (elm.tagName == 'A') {
|
||||
editor.dom.remove(elm, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
execNativeCommand(command);
|
||||
selection.collapse(FALSE);
|
||||
formatter.remove("link");
|
||||
},
|
||||
|
||||
// Override justify commands to use the text formatter engine
|
||||
@@ -301,10 +440,10 @@ define("tinymce/EditorCommands", [
|
||||
selection.select(value);
|
||||
},
|
||||
|
||||
// 插入内容
|
||||
mceInsertContent: function(command, ui, value) {
|
||||
var parser, serializer, parentNode, rootNode, fragment, args;
|
||||
var marker, rng, node, node2, bookmarkHtml;
|
||||
var marker, rng, node, node2, bookmarkHtml, merge;
|
||||
var textInlineElements = editor.schema.getTextInlineElements();
|
||||
|
||||
function trimOrPaddLeftRight(html) {
|
||||
var rng, container, offset;
|
||||
@@ -334,6 +473,62 @@ define("tinymce/EditorCommands", [
|
||||
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);
|
||||
@@ -341,9 +536,8 @@ define("tinymce/EditorCommands", [
|
||||
|
||||
// Setup parser and serializer
|
||||
parser = editor.parser;
|
||||
|
||||
serializer = new Serializer({}, editor.schema);
|
||||
bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark"></span>';
|
||||
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};
|
||||
@@ -359,16 +553,22 @@ define("tinymce/EditorCommands", [
|
||||
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 (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);
|
||||
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();
|
||||
@@ -377,6 +577,8 @@ define("tinymce/EditorCommands", [
|
||||
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') {
|
||||
@@ -384,7 +586,9 @@ define("tinymce/EditorCommands", [
|
||||
|
||||
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');
|
||||
if (editor.schema.isValidChild(node.parent.name, 'span')) {
|
||||
node.parent.insert(marker, node, node.name === 'br');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -438,11 +642,26 @@ define("tinymce/EditorCommands", [
|
||||
// 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) {
|
||||
@@ -452,45 +671,43 @@ define("tinymce/EditorCommands", [
|
||||
}
|
||||
}
|
||||
|
||||
reduceInlineTextElements();
|
||||
|
||||
marker = dom.get('mce_marker');
|
||||
selection.scrollIntoView(marker);
|
||||
|
||||
// Move selection before marker and remove it
|
||||
rng = dom.createRng();
|
||||
try {
|
||||
|
||||
// If previous sibling is a text node set the selection to the end of that node
|
||||
// if(marker) {
|
||||
node = marker.previousSibling;
|
||||
if (node && node.nodeType == 3) {
|
||||
rng.setStart(node, node.nodeValue.length);
|
||||
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);
|
||||
}
|
||||
// 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();
|
||||
// }
|
||||
} catch(e) {
|
||||
|
||||
} 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好
|
||||
@@ -625,6 +842,17 @@ define("tinymce/EditorCommands", [
|
||||
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);
|
||||
},
|
||||
@@ -648,10 +876,10 @@ define("tinymce/EditorCommands", [
|
||||
}
|
||||
|
||||
each(selection.getSelectedBlocks(), function(element) {
|
||||
var indentStyleName;
|
||||
|
||||
if (element.nodeName != "LI") {
|
||||
indentStyleName = dom.getStyle(element, 'direction', true) == 'rtl' ? 'paddingRight' : 'paddingLeft';
|
||||
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);
|
||||
@@ -700,7 +928,7 @@ define("tinymce/EditorCommands", [
|
||||
mceInsertLink: function(command, ui, value) {
|
||||
var anchor;
|
||||
|
||||
if (typeof(value) == 'string') {
|
||||
if (typeof value == 'string') {
|
||||
value = {href: value};
|
||||
}
|
||||
|
||||
@@ -721,21 +949,134 @@ define("tinymce/EditorCommands", [
|
||||
},
|
||||
|
||||
selectAll: function() {
|
||||
var root = dom.getRoot(), rng = dom.createRng();
|
||||
var root = dom.getRoot(), rng;
|
||||
|
||||
// Old IE does a better job with selectall than new versions
|
||||
if (selection.getRng().setStart) {
|
||||
rng = dom.createRng();
|
||||
rng.setStart(root, 0);
|
||||
rng.setEnd(root, root.childNodes.length);
|
||||
|
||||
selection.setRng(rng);
|
||||
} else {
|
||||
execNativeCommand('SelectAll');
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Regular → Executable
+149
-65
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
define("tinymce/EditorManager", [
|
||||
"tinymce/Editor",
|
||||
"tinymce/dom/DomQuery",
|
||||
"tinymce/dom/DOMUtils",
|
||||
"tinymce/util/URI",
|
||||
"tinymce/Env",
|
||||
@@ -27,19 +28,65 @@ define("tinymce/EditorManager", [
|
||||
"tinymce/util/Observable",
|
||||
"tinymce/util/I18n",
|
||||
"tinymce/FocusManager"
|
||||
], function(Editor, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
|
||||
], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
|
||||
var DOM = DOMUtils.DOM;
|
||||
var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
|
||||
var instanceCounter = 0, beforeUnloadDelegate;
|
||||
var instanceCounter = 0, beforeUnloadDelegate, EditorManager;
|
||||
|
||||
function removeEditorFromList(editor) {
|
||||
var editors = EditorManager.editors, removedFromList;
|
||||
|
||||
delete editors[editor.id];
|
||||
|
||||
for (var i = 0; i < editors.length; i++) {
|
||||
if (editors[i] == editor) {
|
||||
editors.splice(i, 1);
|
||||
removedFromList = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Select another editor since the active one was removed
|
||||
if (EditorManager.activeEditor == editor) {
|
||||
EditorManager.activeEditor = editors[0];
|
||||
}
|
||||
|
||||
// Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
|
||||
if (EditorManager.focusedEditor == editor) {
|
||||
EditorManager.focusedEditor = null;
|
||||
}
|
||||
|
||||
return removedFromList;
|
||||
}
|
||||
|
||||
function purgeDestroyedEditor(editor) {
|
||||
// User has manually destroyed the editor lets clean up the mess
|
||||
if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
|
||||
removeEditorFromList(editor);
|
||||
editor.unbindAllNativeEvents();
|
||||
editor.destroy(true);
|
||||
editor = null;
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
EditorManager = {
|
||||
/**
|
||||
* Dom query instance.
|
||||
*
|
||||
* @property $
|
||||
* @type tinymce.dom.DomQuery
|
||||
*/
|
||||
$: DomQuery,
|
||||
|
||||
var EditorManager = {
|
||||
/**
|
||||
* Major version of TinyMCE build.
|
||||
*
|
||||
* @property majorVersion
|
||||
* @type String
|
||||
*/
|
||||
majorVersion : '@@majorVersion@@',
|
||||
majorVersion: '@@majorVersion@@',
|
||||
|
||||
/**
|
||||
* Minor version of TinyMCE build.
|
||||
@@ -47,7 +94,7 @@ define("tinymce/EditorManager", [
|
||||
* @property minorVersion
|
||||
* @type String
|
||||
*/
|
||||
minorVersion : '@@minorVersion@@',
|
||||
minorVersion: '@@minorVersion@@',
|
||||
|
||||
/**
|
||||
* Release date of TinyMCE build.
|
||||
@@ -88,14 +135,21 @@ define("tinymce/EditorManager", [
|
||||
activeEditor: null,
|
||||
|
||||
setup: function() {
|
||||
var self = this, baseURL, documentBaseURL, suffix = "", preInit;
|
||||
var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
|
||||
|
||||
// Get base URL for the current document
|
||||
documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
|
||||
if (!/[\/\\]$/.test(documentBaseURL)) {
|
||||
documentBaseURL += '/';
|
||||
documentBaseURL = document.location.href;
|
||||
|
||||
// Check if the URL is a document based format like: http://site/dir/file and file:///
|
||||
// leave other formats like applewebdata://... intact
|
||||
if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
|
||||
documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
|
||||
|
||||
if (!/[\/\\]$/.test(documentBaseURL)) {
|
||||
documentBaseURL += '/';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If tinymce is defined and has a base use that or use the old tinyMCEPreInit
|
||||
preInit = window.tinymce || window.tinyMCEPreInit;
|
||||
if (preInit) {
|
||||
@@ -105,9 +159,13 @@ define("tinymce/EditorManager", [
|
||||
// Get base where the tinymce script is located
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var src = scripts[i].src;
|
||||
src = scripts[i].src;
|
||||
|
||||
if (/tinymce(\.jquery|)(\.min|\.dev|\.full\.min|)\.js/.test(src)) { // life fix fug full.min
|
||||
// Script types supported:
|
||||
// tinymce.js tinymce.min.js tinymce.dev.js
|
||||
// tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
|
||||
// tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
|
||||
if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
|
||||
if (src.indexOf('.min') != -1) {
|
||||
suffix = '.min';
|
||||
}
|
||||
@@ -116,6 +174,18 @@ define("tinymce/EditorManager", [
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any baseURL by looking at the script elements
|
||||
// Try to use the document.currentScript as a fallback
|
||||
if (!baseURL && document.currentScript) {
|
||||
src = document.currentScript.src;
|
||||
|
||||
if (src.indexOf('.min') != -1) {
|
||||
suffix = '.min';
|
||||
}
|
||||
|
||||
baseURL = src.substring(0, src.lastIndexOf('/'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +240,7 @@ define("tinymce/EditorManager", [
|
||||
* });
|
||||
*/
|
||||
init: function(settings) {
|
||||
var self = this, editors = [], editor;
|
||||
var self = this, editors = [];
|
||||
|
||||
function createId(elm) {
|
||||
var id = elm.id;
|
||||
@@ -192,18 +262,28 @@ define("tinymce/EditorManager", [
|
||||
return id;
|
||||
}
|
||||
|
||||
function execCallback(se, n, s) {
|
||||
var f = se[n];
|
||||
function createEditor(id, settings, targetElm) {
|
||||
if (!purgeDestroyedEditor(self.get(id))) {
|
||||
var editor = new Editor(id, settings, self);
|
||||
|
||||
if (!f) {
|
||||
editor.targetElm = editor.targetElm || targetElm;
|
||||
editors.push(editor);
|
||||
editor.render();
|
||||
}
|
||||
}
|
||||
|
||||
function execCallback(name) {
|
||||
var callback = settings[name];
|
||||
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
|
||||
return callback.apply(self, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
|
||||
function hasClass(n, c) {
|
||||
return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
|
||||
function hasClass(elm, className) {
|
||||
return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
|
||||
}
|
||||
|
||||
function readyHandler() {
|
||||
@@ -211,15 +291,13 @@ define("tinymce/EditorManager", [
|
||||
|
||||
DOM.unbind(window, 'ready', readyHandler);
|
||||
|
||||
execCallback(settings, 'onpageload');
|
||||
execCallback('onpageload');
|
||||
|
||||
if (settings.types) {
|
||||
// Process type specific selector
|
||||
each(settings.types, function(type) {
|
||||
each(DOM.select(type.selector), function(elm) {
|
||||
var editor = new Editor(createId(elm), extend({}, settings, type), self);
|
||||
editors.push(editor);
|
||||
editor.render(1);
|
||||
createEditor(createId(elm), extend({}, settings, type), elm);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -227,12 +305,12 @@ define("tinymce/EditorManager", [
|
||||
} else if (settings.selector) {
|
||||
// Process global selector
|
||||
each(DOM.select(settings.selector), function(elm) {
|
||||
var editor = new Editor(createId(elm), settings, self);
|
||||
editors.push(editor);
|
||||
editor.render(1);
|
||||
createEditor(createId(elm), settings, elm);
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (settings.target) {
|
||||
createEditor(createId(settings.target), settings);
|
||||
}
|
||||
|
||||
// Fallback to old setting
|
||||
@@ -240,22 +318,19 @@ define("tinymce/EditorManager", [
|
||||
case "exact":
|
||||
l = settings.elements || '';
|
||||
|
||||
if(l.length > 0) {
|
||||
each(explode(l), function(v) {
|
||||
if (DOM.get(v)) {
|
||||
editor = new Editor(v, settings, self);
|
||||
editors.push(editor);
|
||||
editor.render(true);
|
||||
if (l.length > 0) {
|
||||
each(explode(l), function(id) {
|
||||
var elm;
|
||||
|
||||
if ((elm = DOM.get(id))) {
|
||||
createEditor(id, settings, elm);
|
||||
} else {
|
||||
each(document.forms, function(f) {
|
||||
each(f.elements, function(e) {
|
||||
if (e.name === v) {
|
||||
v = 'mce_editor_' + instanceCounter++;
|
||||
DOM.setAttrib(e, 'id', v);
|
||||
|
||||
editor = new Editor(v, settings, self);
|
||||
editors.push(editor);
|
||||
editor.render(1);
|
||||
if (e.name === id) {
|
||||
id = 'mce_editor_' + instanceCounter++;
|
||||
DOM.setAttrib(e, 'id', id);
|
||||
createEditor(id, settings, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -272,9 +347,7 @@ define("tinymce/EditorManager", [
|
||||
}
|
||||
|
||||
if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
|
||||
editor = new Editor(createId(elm), settings, self);
|
||||
editors.push(editor);
|
||||
editor.render(true);
|
||||
createEditor(createId(elm), settings, elm);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@@ -294,7 +367,7 @@ define("tinymce/EditorManager", [
|
||||
|
||||
// All done
|
||||
if (l == co) {
|
||||
execCallback(settings, 'oninit');
|
||||
execCallback('oninit');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -303,7 +376,7 @@ define("tinymce/EditorManager", [
|
||||
|
||||
// All done
|
||||
if (l == co) {
|
||||
execCallback(settings, 'oninit');
|
||||
execCallback('oninit');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -332,11 +405,11 @@ define("tinymce/EditorManager", [
|
||||
* });
|
||||
*/
|
||||
get: function(id) {
|
||||
if (id === undefined) {
|
||||
if (!arguments.length) {
|
||||
return this.editors;
|
||||
}
|
||||
|
||||
return this.editors[id];
|
||||
return id in this.editors ? this.editors[id] : null;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -353,6 +426,8 @@ define("tinymce/EditorManager", [
|
||||
editors[editor.id] = editor;
|
||||
editors.push(editor);
|
||||
|
||||
// Doesn't call setActive method since we don't want
|
||||
// to fire a bunch of activate/deactivate calls while initializing
|
||||
self.activeEditor = editor;
|
||||
|
||||
/**
|
||||
@@ -407,7 +482,7 @@ define("tinymce/EditorManager", [
|
||||
* @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
|
||||
*/
|
||||
remove: function(selector) {
|
||||
var self = this, i, editors = self.editors, editor, removedFromList;
|
||||
var self = this, i, editors = self.editors, editor;
|
||||
|
||||
// Remove all editors
|
||||
if (!selector) {
|
||||
@@ -419,11 +494,15 @@ define("tinymce/EditorManager", [
|
||||
}
|
||||
|
||||
// Remove editors by selector
|
||||
if (typeof(selector) == "string") {
|
||||
if (typeof selector == "string") {
|
||||
selector = selector.selector || selector;
|
||||
|
||||
each(DOM.select(selector), function(elm) {
|
||||
self.remove(editors[elm.id]);
|
||||
editor = editors[elm.id];
|
||||
|
||||
if (editor) {
|
||||
self.remove(editor);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -437,28 +516,13 @@ define("tinymce/EditorManager", [
|
||||
return null;
|
||||
}
|
||||
|
||||
delete editors[editor.id];
|
||||
|
||||
for (i = 0; i < editors.length; i++) {
|
||||
if (editors[i] == editor) {
|
||||
editors.splice(i, 1);
|
||||
removedFromList = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Select another editor since the active one was removed
|
||||
if (self.activeEditor == editor) {
|
||||
self.activeEditor = editors[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when an editor is removed from EditorManager collection.
|
||||
*
|
||||
* @event RemoveEditor
|
||||
* @param {Object} e Event arguments.
|
||||
*/
|
||||
if (removedFromList) {
|
||||
if (removeEditorFromList(editor)) {
|
||||
self.fire('RemoveEditor', {editor: editor});
|
||||
}
|
||||
|
||||
@@ -556,6 +620,26 @@ define("tinymce/EditorManager", [
|
||||
*/
|
||||
translate: function(text) {
|
||||
return I18n.translate(text);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the active editor instance and fires the deactivate/activate events.
|
||||
*
|
||||
* @method setActive
|
||||
* @param {tinymce.Editor} editor Editor instance to set as the active instance.
|
||||
*/
|
||||
setActive: function(editor) {
|
||||
var activeEditor = this.activeEditor;
|
||||
|
||||
if (this.activeEditor != editor) {
|
||||
if (activeEditor) {
|
||||
activeEditor.fire('deactivate', {relatedTarget: editor});
|
||||
}
|
||||
|
||||
editor.fire('activate', {relatedTarget: activeEditor});
|
||||
}
|
||||
|
||||
this.activeEditor = editor;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Executable
+204
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* EditorObservable.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This mixin contains the event logic for the tinymce.Editor class.
|
||||
*
|
||||
* @mixin tinymce.EditorObservable
|
||||
* @extends tinymce.util.Observable
|
||||
*/
|
||||
define("tinymce/EditorObservable", [
|
||||
"tinymce/util/Observable",
|
||||
"tinymce/dom/DOMUtils",
|
||||
"tinymce/util/Tools"
|
||||
], function(Observable, DOMUtils, Tools) {
|
||||
var DOM = DOMUtils.DOM, customEventRootDelegates;
|
||||
|
||||
/**
|
||||
* Returns the event target so for the specified event. Some events fire
|
||||
* only on document, some fire on documentElement etc. This also handles the
|
||||
* custom event root setting where it returns that element instead of the body.
|
||||
*
|
||||
* @private
|
||||
* @param {tinymce.Editor} editor Editor instance to get event target from.
|
||||
* @param {String} eventName Name of the event for example "click".
|
||||
* @return {Element/Document} HTML Element or document target to bind on.
|
||||
*/
|
||||
function getEventTarget(editor, eventName) {
|
||||
if (eventName == 'selectionchange') {
|
||||
return editor.getDoc();
|
||||
}
|
||||
|
||||
// Need to bind mousedown/mouseup etc to document not body in iframe mode
|
||||
// Since the user might click on the HTML element not the BODY
|
||||
if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
|
||||
return editor.getDoc().documentElement;
|
||||
}
|
||||
|
||||
// Bind to event root instead of body if it's defined
|
||||
if (editor.settings.event_root) {
|
||||
if (!editor.eventRoot) {
|
||||
editor.eventRoot = DOM.select(editor.settings.event_root)[0];
|
||||
}
|
||||
|
||||
return editor.eventRoot;
|
||||
}
|
||||
|
||||
return editor.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a event delegate for the specified name this delegate will fire
|
||||
* the event to the editor dispatcher.
|
||||
*
|
||||
* @private
|
||||
* @param {tinymce.Editor} editor Editor instance to get event target from.
|
||||
* @param {String} eventName Name of the event for example "click".
|
||||
*/
|
||||
function bindEventDelegate(editor, eventName) {
|
||||
var eventRootElm = getEventTarget(editor, eventName), delegate;
|
||||
|
||||
if (!editor.delegates) {
|
||||
editor.delegates = {};
|
||||
}
|
||||
|
||||
if (editor.delegates[eventName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editor.settings.event_root) {
|
||||
if (!customEventRootDelegates) {
|
||||
customEventRootDelegates = {};
|
||||
editor.editorManager.on('removeEditor', function() {
|
||||
var name;
|
||||
|
||||
if (!editor.editorManager.activeEditor) {
|
||||
if (customEventRootDelegates) {
|
||||
for (name in customEventRootDelegates) {
|
||||
editor.dom.unbind(getEventTarget(editor, name));
|
||||
}
|
||||
|
||||
customEventRootDelegates = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (customEventRootDelegates[eventName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
delegate = function(e) {
|
||||
var target = e.target, editors = editor.editorManager.editors, i = editors.length;
|
||||
|
||||
while (i--) {
|
||||
var body = editors[i].getBody();
|
||||
|
||||
if (body === target || DOM.isChildOf(target, body)) {
|
||||
if (!editors[i].hidden) {
|
||||
editors[i].fire(eventName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
customEventRootDelegates[eventName] = delegate;
|
||||
DOM.bind(eventRootElm, eventName, delegate);
|
||||
} else {
|
||||
delegate = function(e) {
|
||||
if (!editor.hidden) {
|
||||
editor.fire(eventName, e);
|
||||
}
|
||||
};
|
||||
|
||||
DOM.bind(eventRootElm, eventName, delegate);
|
||||
editor.delegates[eventName] = delegate;
|
||||
}
|
||||
}
|
||||
|
||||
var EditorObservable = {
|
||||
/**
|
||||
* Bind any pending event delegates. This gets executed after the target body/document is created.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
bindPendingEventDelegates: function() {
|
||||
var self = this;
|
||||
|
||||
Tools.each(self._pendingNativeEvents, function(name) {
|
||||
bindEventDelegate(self, name);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles a native event on/off this is called by the EventDispatcher when
|
||||
* the first native event handler is added and when the last native event handler is removed.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
toggleNativeEvent: function(name, state) {
|
||||
var self = this;
|
||||
|
||||
if (self.settings.readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Never bind focus/blur since the FocusManager fakes those
|
||||
if (name == "focus" || name == "blur") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state) {
|
||||
if (self.initialized) {
|
||||
bindEventDelegate(self, name);
|
||||
} else {
|
||||
if (!self._pendingNativeEvents) {
|
||||
self._pendingNativeEvents = [name];
|
||||
} else {
|
||||
self._pendingNativeEvents.push(name);
|
||||
}
|
||||
}
|
||||
} else if (self.initialized) {
|
||||
self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
|
||||
delete self.delegates[name];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
unbindAllNativeEvents: function() {
|
||||
var self = this, name;
|
||||
|
||||
if (self.delegates) {
|
||||
for (name in self.delegates) {
|
||||
self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
|
||||
}
|
||||
|
||||
delete self.delegates;
|
||||
}
|
||||
|
||||
if (!self.inline) {
|
||||
self.getBody().onload = null;
|
||||
self.dom.unbind(self.getWin());
|
||||
self.dom.unbind(self.getDoc());
|
||||
}
|
||||
|
||||
self.dom.unbind(self.getBody());
|
||||
self.dom.unbind(self.getContainer());
|
||||
}
|
||||
};
|
||||
|
||||
EditorObservable = Tools.extend({}, Observable, EditorObservable);
|
||||
|
||||
return EditorObservable;
|
||||
});
|
||||
Regular → Executable
+92
-107
@@ -13,16 +13,18 @@
|
||||
*/
|
||||
define("tinymce/EnterKey", [
|
||||
"tinymce/dom/TreeWalker",
|
||||
"tinymce/dom/RangeUtils",
|
||||
"tinymce/Env"
|
||||
], function(TreeWalker, Env) {
|
||||
], function(TreeWalker, RangeUtils, Env) {
|
||||
var isIE = Env.ie && Env.ie < 11;
|
||||
|
||||
return function(editor) {
|
||||
var dom = editor.dom, selection = editor.selection, settings = editor.settings;
|
||||
var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
|
||||
var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(),
|
||||
moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements();
|
||||
|
||||
function handleEnterKey(evt) {
|
||||
var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
|
||||
var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
|
||||
newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
|
||||
|
||||
// Returns true if the block can be split into two blocks or not
|
||||
@@ -51,6 +53,10 @@ define("tinymce/EnterKey", [
|
||||
function trimInlineElementsOnLeftSideOfBlock(block) {
|
||||
var node = block, firstChilds = [], i;
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find inner most first child ex: <p><i><b>*</b></i></p>
|
||||
while ((node = node.firstChild)) {
|
||||
if (dom.isBlock(node)) {
|
||||
@@ -80,7 +86,6 @@ define("tinymce/EnterKey", [
|
||||
// pure whitespace text node or before an image
|
||||
function moveToCaretPosition(root) {
|
||||
var walker, node, rng, lastNode = root, tempElm;
|
||||
|
||||
function firstNonWhiteSpaceNodeSibling(node) {
|
||||
while (node) {
|
||||
if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
|
||||
@@ -91,16 +96,36 @@ define("tinymce/EnterKey", [
|
||||
}
|
||||
}
|
||||
|
||||
if (root.nodeName == 'LI') {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Old IE versions doesn't properly render blocks with br elements in them
|
||||
// For example <p><br></p> wont be rendered correctly in a contentEditable area
|
||||
// until you remove the br producing <p></p>
|
||||
if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
|
||||
if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
|
||||
dom.remove(parentBlock.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
if (/^(LI|DT|DD)$/.test(root.nodeName)) {
|
||||
var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
|
||||
|
||||
if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) {
|
||||
if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
|
||||
root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
rng = dom.createRng();
|
||||
|
||||
// Normalize whitespace to remove empty text nodes. Fix for: #6904
|
||||
// Gecko will be able to place the caret in empty text nodes but it won't render propery
|
||||
// Older IE versions will sometimes crash so for now ignore all IE versions
|
||||
if (!Env.ie) {
|
||||
root.normalize();
|
||||
}
|
||||
|
||||
if (root.hasChildNodes()) {
|
||||
walker = new TreeWalker(root, root);
|
||||
|
||||
@@ -111,7 +136,7 @@ define("tinymce/EnterKey", [
|
||||
break;
|
||||
}
|
||||
|
||||
if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
||||
if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
|
||||
rng.setStartBefore(node);
|
||||
rng.setEndBefore(node);
|
||||
break;
|
||||
@@ -164,7 +189,7 @@ define("tinymce/EnterKey", [
|
||||
// Creates a new block element by cloning the current one or creating a new one if the name is specified
|
||||
// This function will also copy any text formatting from the parent block and add it to the new one
|
||||
function createNewBlock(name) {
|
||||
var node = container, block, clonedNode, caretNode;
|
||||
var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
|
||||
|
||||
if (name || parentBlockName == "TABLE") {
|
||||
block = dom.create(name || newBlockName);
|
||||
@@ -178,7 +203,7 @@ define("tinymce/EnterKey", [
|
||||
// Clone any parent styles
|
||||
if (settings.keep_styles !== false) {
|
||||
do {
|
||||
if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
|
||||
if (textInlineElements[node.nodeName]) {
|
||||
// Never clone a caret containers
|
||||
if (node.id == '_mce_caret') {
|
||||
continue;
|
||||
@@ -339,7 +364,7 @@ define("tinymce/EnterKey", [
|
||||
function getContainerBlock() {
|
||||
var containerBlockParent = containerBlock.parentNode;
|
||||
|
||||
if (containerBlockParent.nodeName == 'LI') {
|
||||
if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
|
||||
return containerBlockParent;
|
||||
}
|
||||
|
||||
@@ -399,56 +424,9 @@ define("tinymce/EnterKey", [
|
||||
undoManager.add();
|
||||
}
|
||||
|
||||
// Walks the parent block to the right and look for BR elements
|
||||
function hasRightSideContent() {
|
||||
var walker = new TreeWalker(container, parentBlock), node;
|
||||
|
||||
while ((node = walker.next())) {
|
||||
if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts a BR element if the forced_root_block option is set to false or empty string
|
||||
function insertBr() {
|
||||
var brElm, extraBr, marker;
|
||||
|
||||
if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
|
||||
// Insert extra BR element at the end block elements
|
||||
if (!isIE && !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
|
||||
if (isIE && 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);
|
||||
undoManager.add();
|
||||
editor.execCommand("InsertLineBreak", false, evt);
|
||||
}
|
||||
|
||||
// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
|
||||
@@ -495,59 +473,65 @@ define("tinymce/EnterKey", [
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any selected contents
|
||||
if (!rng.collapsed) {
|
||||
editor.execCommand('Delete');
|
||||
return;
|
||||
}
|
||||
rng = selection.getRng(true);
|
||||
|
||||
// Event is blocked by some other handler for example the lists plugin
|
||||
if (evt.isDefaultPrevented()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup range items and newBlockName
|
||||
container = rng.startContainer;
|
||||
|
||||
// life ace, 在ace editor下回车给ace来控制
|
||||
var $container = $(container);
|
||||
var aceEditorAndPre = LeaAce.isInAce($container);
|
||||
if(aceEditorAndPre) {
|
||||
// alert(2);
|
||||
// 跳出aceEditor
|
||||
if(evt.shiftKey) {
|
||||
// alert(33);
|
||||
// var id = $container.attr('id');
|
||||
var aceEditor = aceEditorAndPre[0];
|
||||
aceEditor.blur();
|
||||
var pre = aceEditorAndPre[1];
|
||||
// 必须要延迟
|
||||
setTimeout(function() {
|
||||
aceEditor.blur();
|
||||
// log('blur...')
|
||||
var newBlock = $("<p><br /></p>");
|
||||
pre.after(newBlock);
|
||||
// log(newBlock.get(0));
|
||||
rng.setStart(newBlock.get(0), 0);
|
||||
rng.setEnd(newBlock.get(0), 0);
|
||||
// 没用
|
||||
rng.selectNode(newBlock.get(0));
|
||||
selection.setRng(rng);
|
||||
// 再延迟
|
||||
setTimeout(function() {
|
||||
selection.setRng(rng);
|
||||
}, 10);
|
||||
|
||||
// TODO ... 这里
|
||||
// log("why");
|
||||
// log(rng);
|
||||
// log(selection.getRng());
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// Delete any selected contents
|
||||
if (!rng.collapsed) {
|
||||
editor.execCommand('Delete');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Setup range items and newBlockName
|
||||
new RangeUtils(dom).normalize(rng);
|
||||
container = rng.startContainer;
|
||||
|
||||
// life ace, 在ace editor下回车给ace来控制
|
||||
if(window.LeaAce && LeaAce.isInAce && window.$) {
|
||||
var $container = $(container);
|
||||
var aceEditorAndPre = LeaAce.isInAce($container);
|
||||
if(aceEditorAndPre) {
|
||||
// alert(2);
|
||||
// 跳出aceEditor
|
||||
if(evt.shiftKey) {
|
||||
// alert(33);
|
||||
// var id = $container.attr('id');
|
||||
var aceEditor = aceEditorAndPre[0];
|
||||
aceEditor.blur();
|
||||
var pre = aceEditorAndPre[1];
|
||||
// 必须要延迟
|
||||
setTimeout(function() {
|
||||
aceEditor.blur();
|
||||
// log('blur...')
|
||||
var newBlock = $("<p><br /></p>");
|
||||
pre.after(newBlock);
|
||||
// log(newBlock.get(0));
|
||||
rng.setStart(newBlock.get(0), 0);
|
||||
rng.setEnd(newBlock.get(0), 0);
|
||||
// 没用
|
||||
rng.selectNode(newBlock.get(0));
|
||||
selection.setRng(rng);
|
||||
// 再延迟
|
||||
setTimeout(function() {
|
||||
selection.setRng(rng);
|
||||
}, 10);
|
||||
|
||||
// TODO ... 这里
|
||||
// log("why");
|
||||
// log(rng);
|
||||
// log(selection.getRng());
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
offset = rng.startOffset;
|
||||
newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
|
||||
newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
|
||||
@@ -557,6 +541,7 @@ define("tinymce/EnterKey", [
|
||||
// Resolve node index
|
||||
if (container.nodeType == 1 && container.hasChildNodes()) {
|
||||
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;
|
||||
@@ -605,8 +590,8 @@ define("tinymce/EnterKey", [
|
||||
parentBlockName = containerBlockName;
|
||||
}
|
||||
|
||||
// Handle enter in LI
|
||||
if (parentBlockName == 'LI') {
|
||||
// Handle enter in list item
|
||||
if (/^(LI|DT|DD)$/.test(parentBlockName)) {
|
||||
if (!newBlockName && shiftKey) {
|
||||
insertBr();
|
||||
return;
|
||||
@@ -680,7 +665,7 @@ define("tinymce/EnterKey", [
|
||||
dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
|
||||
|
||||
// Allow custom handling of new blocks
|
||||
editor.fire('NewBlock', { newBlock: newBlock });
|
||||
editor.fire('NewBlock', {newBlock: newBlock});
|
||||
|
||||
undoManager.add();
|
||||
}
|
||||
|
||||
Regular → Executable
+13
-3
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class contains various environment constrants like browser versions etc.
|
||||
* This class contains various environment constants like browser versions etc.
|
||||
* Normally you don't want to sniff specific browser versions but sometimes you have
|
||||
* to when it's impossible to feature detect. So use this with care.
|
||||
*
|
||||
@@ -18,9 +18,10 @@
|
||||
*/
|
||||
define("tinymce/Env", [], function() {
|
||||
var nav = navigator, userAgent = nav.userAgent;
|
||||
var opera, webkit, ie, ie11, gecko, mac, iDevice;
|
||||
var opera, webkit, ie, ie11, gecko, mac, iDevice, android;
|
||||
|
||||
opera = window.opera && window.opera.buildNumber;
|
||||
android = /Android/.test(userAgent);
|
||||
webkit = /WebKit/.test(userAgent);
|
||||
ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
|
||||
ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
|
||||
@@ -89,6 +90,15 @@ define("tinymce/Env", [], function() {
|
||||
*/
|
||||
iOS: iDevice,
|
||||
|
||||
/**
|
||||
* Constant that is true if the os is android.
|
||||
*
|
||||
* @property android
|
||||
* @type Boolean
|
||||
* @final
|
||||
*/
|
||||
android: android,
|
||||
|
||||
/**
|
||||
* Constant that is true if the browser supports editing.
|
||||
*
|
||||
@@ -132,4 +142,4 @@ define("tinymce/Env", [], function() {
|
||||
*/
|
||||
documentMode: ie ? (document.documentMode || 7) : 10
|
||||
};
|
||||
});
|
||||
});
|
||||
Regular → Executable
+126
-67
@@ -21,6 +21,8 @@ define("tinymce/FocusManager", [
|
||||
"tinymce/dom/DOMUtils",
|
||||
"tinymce/Env"
|
||||
], function(DOMUtils, Env) {
|
||||
var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
|
||||
|
||||
/**
|
||||
* Constructs a new focus manager instance.
|
||||
*
|
||||
@@ -40,8 +42,13 @@ define("tinymce/FocusManager", [
|
||||
|
||||
// We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
|
||||
// TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
|
||||
function createBookmark(rng) {
|
||||
function createBookmark(dom, rng) {
|
||||
if (rng && rng.startContainer) {
|
||||
// Verify that the range is within the root of the editor
|
||||
if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
startContainer: rng.startContainer,
|
||||
startOffset: rng.startOffset,
|
||||
@@ -67,76 +74,73 @@ define("tinymce/FocusManager", [
|
||||
return rng;
|
||||
}
|
||||
|
||||
function registerEvents(e) {
|
||||
var editor = e.editor, lastRng, selectionChangeHandler;
|
||||
function isUIElement(elm) {
|
||||
return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
|
||||
}
|
||||
|
||||
function isUIElement(elm) {
|
||||
return !!DOMUtils.DOM.getParent(elm, FocusManager.isEditorUIElement);
|
||||
}
|
||||
function registerEvents(e) {
|
||||
var editor = e.editor;
|
||||
|
||||
editor.on('init', function() {
|
||||
// On IE take selection snapshot onbeforedeactivate
|
||||
if ("onbeforedeactivate" in document && Env.ie < 11) {
|
||||
editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
|
||||
var ieSelection = editor.getDoc().selection;
|
||||
|
||||
try {
|
||||
lastRng = ieSelection && ieSelection.createRange ? ieSelection.createRange() : editor.selection.getRng();
|
||||
} catch (ex) {
|
||||
// IE throws "Unexcpected call to method or property access" some times so lets ignore it
|
||||
}
|
||||
});
|
||||
} else if (editor.inline || Env.ie > 10) {
|
||||
// On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
|
||||
editor.on('nodechange keyup', function() {
|
||||
var isInBody, node = document.activeElement;
|
||||
|
||||
// IE 11 reports active element as iframe not body of iframe
|
||||
if (node && node.id == editor.id + '_ifr') {
|
||||
node = editor.getBody();
|
||||
}
|
||||
|
||||
// Check if selection is within editor body
|
||||
while (node) {
|
||||
if (node == editor.getBody()) {
|
||||
isInBody = true;
|
||||
break;
|
||||
// Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
|
||||
if (editor.inline || Env.ie) {
|
||||
// Use the onbeforedeactivate event when available since it works better see #7023
|
||||
if ("onbeforedeactivate" in document && Env.ie < 9) {
|
||||
editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
|
||||
if (e.target != editor.getBody()) {
|
||||
return;
|
||||
}
|
||||
|
||||
node = node.parentNode;
|
||||
}
|
||||
try {
|
||||
editor.lastRng = editor.selection.getRng();
|
||||
} catch (ex) {
|
||||
// IE throws "Unexcpected call to method or property access" some times so lets ignore it
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
|
||||
editor.on('nodechange mouseup keyup', function(e) {
|
||||
var node = getActiveElement();
|
||||
|
||||
if (isInBody) {
|
||||
lastRng = editor.selection.getRng();
|
||||
}
|
||||
});
|
||||
// Only act on manual nodechanges
|
||||
if (e.type == 'nodechange' && e.selectionChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IE 11 reports active element as iframe not body of iframe
|
||||
if (node && node.id == editor.id + '_ifr') {
|
||||
node = editor.getBody();
|
||||
}
|
||||
|
||||
if (editor.dom.isChildOf(node, editor.getBody())) {
|
||||
editor.lastRng = editor.selection.getRng();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handles the issue with WebKit not retaining selection within inline document
|
||||
// If the user releases the mouse out side the body while selecting a nodeChange won't
|
||||
// fire and there for the selection snapshot won't be stored
|
||||
// TODO: Optimize this since we only need to bind these on the active editor
|
||||
if (Env.webkit) {
|
||||
// If the user releases the mouse out side the body since a mouse up event wont occur on the body
|
||||
if (Env.webkit && !selectionChangeHandler) {
|
||||
selectionChangeHandler = function() {
|
||||
var rng = editor.selection.getRng();
|
||||
var activeEditor = editorManager.activeEditor;
|
||||
|
||||
// Store when it's non collapsed
|
||||
if (!rng.collapsed) {
|
||||
lastRng = rng;
|
||||
if (activeEditor && activeEditor.selection) {
|
||||
var rng = activeEditor.selection.getRng();
|
||||
|
||||
// Store when it's non collapsed
|
||||
if (rng && !rng.collapsed) {
|
||||
editor.lastRng = rng;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Bind selection handler
|
||||
DOMUtils.DOM.bind(document, 'selectionchange', selectionChangeHandler);
|
||||
|
||||
editor.on('remove', function() {
|
||||
DOMUtils.DOM.unbind(document, 'selectionchange', selectionChangeHandler);
|
||||
});
|
||||
DOM.bind(document, 'selectionchange', selectionChangeHandler);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
editor.on('setcontent', function() {
|
||||
lastRng = null;
|
||||
editor.lastRng = null;
|
||||
});
|
||||
|
||||
// Remove last selection bookmark on mousedown see #6305
|
||||
@@ -157,37 +161,91 @@ define("tinymce/FocusManager", [
|
||||
focusedEditor.fire('blur', {focusedEditor: editor});
|
||||
}
|
||||
|
||||
editorManager.activeEditor = editor;
|
||||
editor.fire('focus', {blurredEditor: focusedEditor});
|
||||
editor.focus(false);
|
||||
editorManager.setActive(editor);
|
||||
editorManager.focusedEditor = editor;
|
||||
editor.fire('focus', {blurredEditor: focusedEditor});
|
||||
editor.focus(true);
|
||||
}
|
||||
|
||||
lastRng = null;
|
||||
editor.lastRng = null;
|
||||
});
|
||||
|
||||
editor.on('focusout', function() {
|
||||
editor.selection.lastFocusBookmark = createBookmark(lastRng);
|
||||
|
||||
window.setTimeout(function() {
|
||||
var focusedEditor = editorManager.focusedEditor;
|
||||
|
||||
// Focus from editorA into editorB then don't restore selection
|
||||
if (focusedEditor != editor) {
|
||||
editor.selection.lastFocusBookmark = null;
|
||||
}
|
||||
|
||||
// Still the same editor the the blur was outside any editor UI
|
||||
if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
|
||||
editor.fire('blur', {focusedEditor: null});
|
||||
editorManager.focusedEditor = null;
|
||||
editor.selection.lastFocusBookmark = null;
|
||||
|
||||
// Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
|
||||
if (editor.selection) {
|
||||
editor.selection.lastFocusBookmark = null;
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Check if focus is moved to an element outside the active editor by checking if the target node
|
||||
// isn't within the body of the activeEditor nor a UI element such as a dialog child control
|
||||
if (!documentFocusInHandler) {
|
||||
documentFocusInHandler = function(e) {
|
||||
var activeEditor = editorManager.activeEditor;
|
||||
|
||||
if (activeEditor && e.target.ownerDocument == document) {
|
||||
// Check to make sure we have a valid selection don't update the bookmark if it's
|
||||
// a focusin to the body of the editor see #7025
|
||||
if (activeEditor.selection && e.target != activeEditor.getBody()) {
|
||||
activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
|
||||
}
|
||||
|
||||
// Fire a blur event if the element isn't a UI element
|
||||
if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
|
||||
activeEditor.fire('blur', {focusedEditor: null});
|
||||
editorManager.focusedEditor = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DOM.bind(document, 'focusin', documentFocusInHandler);
|
||||
}
|
||||
|
||||
// Handle edge case when user starts the selection inside the editor and releases
|
||||
// the mouse outside the editor producing a new selection. This weird workaround is needed since
|
||||
// Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
|
||||
if (editor.inline && !documentMouseUpHandler) {
|
||||
documentMouseUpHandler = function(e) {
|
||||
var activeEditor = editorManager.activeEditor;
|
||||
|
||||
if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
|
||||
var rng = activeEditor.selection.getRng();
|
||||
|
||||
if (!rng.collapsed) {
|
||||
activeEditor.lastRng = rng;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DOM.bind(document, 'mouseup', documentMouseUpHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function unregisterDocumentEvents(e) {
|
||||
if (editorManager.focusedEditor == e.editor) {
|
||||
editorManager.focusedEditor = null;
|
||||
}
|
||||
|
||||
if (!editorManager.activeEditor) {
|
||||
DOM.unbind(document, 'selectionchange', selectionChangeHandler);
|
||||
DOM.unbind(document, 'focusin', documentFocusInHandler);
|
||||
DOM.unbind(document, 'mouseup', documentMouseUpHandler);
|
||||
selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
editorManager.on('AddEditor', registerEvents);
|
||||
editorManager.on('RemoveEditor', unregisterDocumentEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,8 +256,9 @@ define("tinymce/FocusManager", [
|
||||
* @return {Boolean} True/false state if the element is part of the UI or not.
|
||||
*/
|
||||
FocusManager.isEditorUIElement = function(elm) {
|
||||
return elm.className.indexOf('mce-') !== -1;
|
||||
// Needs to be converted to string since svg can have focus: #6776
|
||||
return elm.className.toString().indexOf('mce-') !== -1;
|
||||
};
|
||||
|
||||
return FocusManager;
|
||||
});
|
||||
});
|
||||
Regular → Executable
-1
@@ -14,7 +14,6 @@ define("tinymce/ForceBlocks", [], function() {
|
||||
var schema = editor.schema, blockElements = schema.getBlockElements();
|
||||
|
||||
function addRootBlocks() {
|
||||
|
||||
var node = selection.getStart(), rootNode = editor.getBody(), rng;
|
||||
var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
|
||||
var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
|
||||
|
||||
Regular → Executable
+140
-237
@@ -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
|
||||
|
||||
Regular → Executable
+6
-3
@@ -54,9 +54,12 @@ define("tinymce/LegacyInput", [
|
||||
},
|
||||
|
||||
u: function(dom, node) {
|
||||
replaceWithSpan(node, {
|
||||
textDecoration: 'underline'
|
||||
});
|
||||
// HTML5 allows U element
|
||||
if (editor.settings.schema === "html4") {
|
||||
replaceWithSpan(node, {
|
||||
textDecoration: 'underline'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
strike: function(dom, node) {
|
||||
|
||||
Executable
+153
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* NodeChange.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class handles the nodechange event dispatching both manual and though selection change events.
|
||||
*
|
||||
* @class tinymce.NodeChange
|
||||
* @private
|
||||
*/
|
||||
define("tinymce/NodeChange", [
|
||||
"tinymce/dom/RangeUtils",
|
||||
"tinymce/Env"
|
||||
], function(RangeUtils, Env) {
|
||||
return function(editor) {
|
||||
var lastRng, lastPath = [];
|
||||
|
||||
/**
|
||||
* Returns true/false if the current element path has been changed or not.
|
||||
*
|
||||
* @private
|
||||
* @return {Boolean} True if the element path is the same false if it's not.
|
||||
*/
|
||||
function isSameElementPath(startElm) {
|
||||
var i, currentPath;
|
||||
|
||||
currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
|
||||
if (currentPath.length === lastPath.length) {
|
||||
for (i = currentPath.length; i >= 0; i--) {
|
||||
if (currentPath[i] !== lastPath[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i === -1) {
|
||||
lastPath = currentPath;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
lastPath = currentPath;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gecko doesn't support the "selectionchange" event
|
||||
if (!('onselectionchange' in editor.getDoc())) {
|
||||
editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
|
||||
var nativeRng, fakeRng;
|
||||
|
||||
// Since DOM Ranges mutate on modification
|
||||
// of the DOM we need to clone it's contents
|
||||
nativeRng = editor.selection.getRng();
|
||||
fakeRng = {
|
||||
startContainer: nativeRng.startContainer,
|
||||
startOffset: nativeRng.startOffset,
|
||||
endContainer: nativeRng.endContainer,
|
||||
endOffset: nativeRng.endOffset
|
||||
};
|
||||
|
||||
// Always treat nodechange as a selectionchange since applying
|
||||
// formatting to the current range wouldn't update the range but it's parent
|
||||
if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
|
||||
editor.fire('SelectionChange');
|
||||
}
|
||||
|
||||
lastRng = fakeRng;
|
||||
});
|
||||
}
|
||||
|
||||
// IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
|
||||
// When the contextmenu event fires the selection is located at the right location
|
||||
editor.on('contextmenu', function() {
|
||||
editor.fire('SelectionChange');
|
||||
});
|
||||
|
||||
// Selection change is delayed ~200ms on IE when you click inside the current range
|
||||
editor.on('SelectionChange', function() {
|
||||
var startElm = editor.selection.getStart(true);
|
||||
|
||||
// IE 8 will fire a selectionchange event with an incorrect selection
|
||||
// when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event
|
||||
if (!Env.range && editor.selection.isCollapsed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
|
||||
editor.nodeChanged({selectionChange: true});
|
||||
}
|
||||
});
|
||||
|
||||
// Fire an extra nodeChange on mouseup for compatibility reasons
|
||||
editor.on('MouseUp', function(e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
// Delay nodeChanged call for WebKit edge case issue where the range
|
||||
// isn't updated until after you click outside a selected image
|
||||
if (editor.selection.getNode().nodeName == 'IMG') {
|
||||
setTimeout(function() {
|
||||
editor.nodeChanged();
|
||||
}, 0);
|
||||
} else {
|
||||
editor.nodeChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Distpaches out a onNodeChange event to all observers. This method should be called when you
|
||||
* need to update the UI states or element path etc.
|
||||
*
|
||||
* @method nodeChanged
|
||||
* @param {Object} args Optional args to pass to NodeChange event handlers.
|
||||
*/
|
||||
this.nodeChanged = function(args) {
|
||||
var selection = editor.selection, node, parents, root;
|
||||
|
||||
// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
|
||||
if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.settings.readonly) {
|
||||
// Get start node
|
||||
root = editor.getBody();
|
||||
node = selection.getStart() || root;
|
||||
node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node;
|
||||
|
||||
// Edge case for <p>|<img></p>
|
||||
if (node.nodeName == 'IMG' && selection.isCollapsed()) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
// Get parents and add them to object
|
||||
parents = [];
|
||||
editor.dom.getParent(node, function(node) {
|
||||
if (node === root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
parents.push(node);
|
||||
});
|
||||
|
||||
args = args || {};
|
||||
args.element = node;
|
||||
args.parents = parents;
|
||||
|
||||
editor.fire('NodeChange', args);
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
Regular → Executable
+95
-35
@@ -10,6 +10,12 @@
|
||||
|
||||
/**
|
||||
* Contains all logic for handling of keyboard shortcuts.
|
||||
*
|
||||
* @example
|
||||
* editor.shortcuts.add('ctrl+a', function() {});
|
||||
* editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
|
||||
* editor.shortcuts.add('ctrl+alt+a', function() {});
|
||||
* editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
|
||||
*/
|
||||
define("tinymce/Shortcuts", [
|
||||
"tinymce/util/Tools",
|
||||
@@ -23,15 +29,78 @@ define("tinymce/Shortcuts", [
|
||||
"f11": 122
|
||||
};
|
||||
|
||||
var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
|
||||
|
||||
return function(editor) {
|
||||
var self = this, shortcuts = {};
|
||||
|
||||
editor.on('keyup keypress keydown', function(e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey) {
|
||||
each(shortcuts, function(shortcut) {
|
||||
var ctrlKey = Env.mac ? (e.ctrlKey || e.metaKey) : e.ctrlKey;
|
||||
function createShortcut(pattern, desc, cmdFunc, scope) {
|
||||
var id, key, shortcut;
|
||||
|
||||
if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
|
||||
shortcut = {
|
||||
func: cmdFunc,
|
||||
scope: scope || editor,
|
||||
desc: editor.translate(desc)
|
||||
};
|
||||
|
||||
// Parse modifiers and keys ctrl+alt+b for example
|
||||
each(explode(pattern, '+'), function(value) {
|
||||
if (value in modifierNames) {
|
||||
shortcut[value] = true;
|
||||
} else {
|
||||
// Allow numeric keycodes like ctrl+219 for ctrl+[
|
||||
if (/^[0-9]{2,}$/.test(value)) {
|
||||
shortcut.keyCode = parseInt(value, 10);
|
||||
} else {
|
||||
shortcut.charCode = value.charCodeAt(0);
|
||||
shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Generate unique id for modifier combination and set default state for unused modifiers
|
||||
id = [shortcut.keyCode];
|
||||
for (key in modifierNames) {
|
||||
if (shortcut[key]) {
|
||||
id.push(key);
|
||||
} else {
|
||||
shortcut[key] = false;
|
||||
}
|
||||
}
|
||||
shortcut.id = id.join(',');
|
||||
|
||||
// Handle special access modifier differently depending on Mac/Win
|
||||
if (shortcut.access) {
|
||||
shortcut.alt = true;
|
||||
|
||||
if (Env.mac) {
|
||||
shortcut.ctrl = true;
|
||||
} else {
|
||||
shortcut.shift = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle special meta modifier differently depending on Mac/Win
|
||||
if (shortcut.meta) {
|
||||
if (Env.mac) {
|
||||
shortcut.meta = true;
|
||||
} else {
|
||||
shortcut.ctrl = true;
|
||||
shortcut.meta = false;
|
||||
}
|
||||
}
|
||||
|
||||
return shortcut;
|
||||
}
|
||||
|
||||
editor.on('keyup keypress keydown', function(e) {
|
||||
if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) {
|
||||
each(shortcuts, function(shortcut) {
|
||||
if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +132,7 @@ define("tinymce/Shortcuts", [
|
||||
|
||||
cmd = cmdFunc;
|
||||
|
||||
if (typeof(cmdFunc) === 'string') {
|
||||
if (typeof cmdFunc === 'string') {
|
||||
cmdFunc = function() {
|
||||
editor.execCommand(cmd, false, null);
|
||||
};
|
||||
@@ -74,38 +143,29 @@ define("tinymce/Shortcuts", [
|
||||
}
|
||||
|
||||
each(explode(pattern.toLowerCase()), function(pattern) {
|
||||
var shortcut = {
|
||||
func: cmdFunc,
|
||||
scope: scope || editor,
|
||||
desc: editor.translate(desc),
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
shift: false
|
||||
};
|
||||
|
||||
each(explode(pattern, '+'), function(value) {
|
||||
switch (value) {
|
||||
case 'alt':
|
||||
case 'ctrl':
|
||||
case 'shift':
|
||||
shortcut[value] = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
shortcut.charCode = value.charCodeAt(0);
|
||||
shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
});
|
||||
|
||||
shortcuts[
|
||||
(shortcut.ctrl ? 'ctrl' : '') + ',' +
|
||||
(shortcut.alt ? 'alt' : '') + ',' +
|
||||
(shortcut.shift ? 'shift' : '') + ',' +
|
||||
shortcut.keyCode
|
||||
] = shortcut;
|
||||
var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
|
||||
shortcuts[shortcut.id] = shortcut;
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a keyboard shortcut by pattern.
|
||||
*
|
||||
* @method remove
|
||||
* @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
|
||||
* @return {Boolean} true/false state if the shortcut was removed or not.
|
||||
*/
|
||||
self.remove = function(pattern) {
|
||||
var shortcut = createShortcut(pattern);
|
||||
|
||||
if (shortcuts[shortcut.id]) {
|
||||
delete shortcuts[shortcut.id];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
};
|
||||
});
|
||||
Regular → Executable
+84
-57
@@ -14,36 +14,68 @@
|
||||
* @class tinymce.UndoManager
|
||||
*/
|
||||
define("tinymce/UndoManager", [
|
||||
"tinymce/util/VK",
|
||||
"tinymce/Env",
|
||||
"tinymce/util/Tools"
|
||||
], function(Env, Tools) {
|
||||
"tinymce/util/Tools",
|
||||
"tinymce/html/SaxParser"
|
||||
], function(VK, Env, Tools, SaxParser) {
|
||||
var trim = Tools.trim, trimContentRegExp;
|
||||
|
||||
trimContentRegExp = new RegExp([
|
||||
'<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
|
||||
'<div[^>]+data-mce-bogus[^>]+><\\/div>', // Trim bogus divs like resize handles
|
||||
'\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
|
||||
].join('|'), 'gi');
|
||||
|
||||
return function(editor) {
|
||||
var self, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, lock;
|
||||
var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
|
||||
|
||||
// life ace
|
||||
var canAdd = true;
|
||||
|
||||
// Returns a trimmed version of the current editor contents
|
||||
/**
|
||||
* Returns a trimmed version of the editor contents to be used for the undo level. This
|
||||
* will remove any data-mce-bogus="all" marked elements since these are used for UI it will also
|
||||
* remove the data-mce-selected attributes used for selection of objects and caret containers.
|
||||
* It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will
|
||||
* be removed by the serialization logic when you save.
|
||||
*
|
||||
* @private
|
||||
* @return {String} HTML contents of the editor excluding some internal bogus elements.
|
||||
*/
|
||||
function getContent() {
|
||||
// TODO 性能有待检测
|
||||
// life ace editor, 获取真正的内容, 只有<pre>
|
||||
return getEditorContent();
|
||||
// 这个有XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
// return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, ''));
|
||||
// life ace
|
||||
if(window.LeaAce && window.getEditorContent) {
|
||||
return getEditorContent();
|
||||
}
|
||||
|
||||
var content = editor.getContent({format: 'raw', no_events: 1});
|
||||
var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
|
||||
var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;
|
||||
|
||||
content = content.replace(trimContentRegExp, '');
|
||||
shortEndedElements = schema.getShortEndedElements();
|
||||
|
||||
// Remove all bogus elements marked with "all"
|
||||
while ((matches = bogusAllRegExp.exec(content))) {
|
||||
index = bogusAllRegExp.lastIndex;
|
||||
matchLength = matches[0].length;
|
||||
|
||||
if (shortEndedElements[matches[1]]) {
|
||||
endTagIndex = index;
|
||||
} else {
|
||||
endTagIndex = SaxParser.findEndTag(schema, content, index);
|
||||
}
|
||||
|
||||
content = content.substring(0, index - matchLength) + content.substring(endTagIndex);
|
||||
bogusAllRegExp.lastIndex = index - matchLength;
|
||||
}
|
||||
|
||||
return trim(content);
|
||||
}
|
||||
|
||||
function addNonTypingUndoLevel() {
|
||||
function addNonTypingUndoLevel(e) {
|
||||
self.typing = false;
|
||||
// log('addNonTypingUndoLevel')
|
||||
self.add();
|
||||
self.add({}, e);
|
||||
}
|
||||
|
||||
// Add initial undo level when the editor is initialized
|
||||
@@ -65,7 +97,7 @@ define("tinymce/UndoManager", [
|
||||
var cmd = e.command;
|
||||
|
||||
if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
|
||||
self.add();
|
||||
addNonTypingUndoLevel(e);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -73,25 +105,21 @@ define("tinymce/UndoManager", [
|
||||
self.beforeChange();
|
||||
});
|
||||
|
||||
editor.on('SaveContent ObjectResized', addNonTypingUndoLevel);
|
||||
editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
|
||||
editor.dom.bind(editor.getBody(), 'focusout', function() {
|
||||
if (!editor.removed && self.typing) {
|
||||
addNonTypingUndoLevel();
|
||||
}
|
||||
});
|
||||
editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
|
||||
editor.on('DragEnd', addNonTypingUndoLevel);
|
||||
|
||||
editor.on('KeyUp', function(e) {
|
||||
var keyCode = e.keyCode;
|
||||
|
||||
// ctrl + shift + c, command + shift + c 代码
|
||||
// life ace
|
||||
// ctrl + shift + c, command + shift + c 代码
|
||||
if((e.metaKey && e.shiftKey) || (e.ctrlKey && e.shiftKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// life ace
|
||||
// 在ace中回车也会加history
|
||||
if(keyCode == 13 && LeaAce.nowIsInAce()) {
|
||||
if(keyCode == 13 && window.LeaAce && LeaAce.nowIsInAce()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,7 +128,6 @@ define("tinymce/UndoManager", [
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
// 8 表示删除
|
||||
if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
|
||||
editor.nodeChanged();
|
||||
}
|
||||
@@ -125,8 +152,8 @@ define("tinymce/UndoManager", [
|
||||
|
||||
editor.on('KeyDown', function(e) {
|
||||
var keyCode = e.keyCode;
|
||||
log('keyCode' + keyCode)
|
||||
|
||||
// life ace
|
||||
// 在ace中回车也会加history
|
||||
if(keyCode == 13/* && LeaAce.nowIsInAce()*/) {
|
||||
return;
|
||||
@@ -135,44 +162,39 @@ define("tinymce/UndoManager", [
|
||||
// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
|
||||
if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
|
||||
if (self.typing) {
|
||||
addNonTypingUndoLevel();
|
||||
addNonTypingUndoLevel(e);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ctrl + shift + c, command + shift + c 代码
|
||||
// life ace
|
||||
// ctrl + shift + c, command + shift + c 代码
|
||||
if((e.metaKey && e.shiftKey) || (e.ctrlKey || e.shiftKey)) {
|
||||
log("no add history");
|
||||
return;
|
||||
}
|
||||
|
||||
// ctrl + z
|
||||
if(e.metaKey && keyCode == 90 || (e.ctrlKey && keyCode == 90)) {
|
||||
log('ctrl + z');
|
||||
return;
|
||||
}
|
||||
|
||||
// If key isn't shift,ctrl,alt,capslock,metakey
|
||||
if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
|
||||
var modKey = VK.modifierPressed(e);
|
||||
if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) {
|
||||
self.beforeChange();
|
||||
self.typing = true;
|
||||
self.add();
|
||||
self.add({}, e);
|
||||
isFirstTypedCharacter = true;
|
||||
}
|
||||
});
|
||||
|
||||
editor.on('MouseDown', function() {
|
||||
editor.on('MouseDown', function(e) {
|
||||
if (self.typing) {
|
||||
addNonTypingUndoLevel();
|
||||
addNonTypingUndoLevel(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Add keyboard shortcuts for undo/redo keys
|
||||
editor.addShortcut('ctrl+z', '', 'Undo');
|
||||
editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo');
|
||||
editor.addShortcut('meta+z', '', 'Undo');
|
||||
editor.addShortcut('meta+y,meta+shift+z', '', 'Redo');
|
||||
|
||||
editor.on('AddUndo Undo Redo ClearUndos MouseUp', function(e) {
|
||||
editor.on('AddUndo Undo Redo ClearUndos', function(e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
editor.nodeChanged();
|
||||
}
|
||||
@@ -197,11 +219,12 @@ define("tinymce/UndoManager", [
|
||||
* @method beforeChange
|
||||
*/
|
||||
beforeChange: function() {
|
||||
if (!lock) {
|
||||
if (!locks) {
|
||||
beforeBookmark = editor.selection.getBookmark(2, true);
|
||||
}
|
||||
},
|
||||
|
||||
// life ace
|
||||
setCanAdd: function(status) {
|
||||
canAdd = status;
|
||||
},
|
||||
@@ -210,26 +233,31 @@ define("tinymce/UndoManager", [
|
||||
* Adds a new undo level/snapshot to the undo list.
|
||||
*
|
||||
* @method add
|
||||
* @param {Object} l Optional undo level object to add.
|
||||
* @param {Object} level Optional undo level object to add.
|
||||
* @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
|
||||
* @return {Object} Undo level that got added or null it a level wasn't needed.
|
||||
*/
|
||||
add: function(level) {
|
||||
add: function(level, event) {
|
||||
// life ace
|
||||
if(!canAdd) {
|
||||
log('cant add history')
|
||||
return;
|
||||
}
|
||||
log('add history')
|
||||
|
||||
var i, settings = editor.settings, lastLevel;
|
||||
|
||||
level = level || {};
|
||||
level.content = getContent();
|
||||
|
||||
if (lock || editor.fire('BeforeAddUndo', {level: level}).isDefaultPrevented()) {
|
||||
if (locks || editor.removed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
lastLevel = data[index];
|
||||
if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add undo level if needed
|
||||
lastLevel = data[index];
|
||||
if (lastLevel && lastLevel.content == level.content) {
|
||||
return null;
|
||||
}
|
||||
@@ -262,13 +290,13 @@ define("tinymce/UndoManager", [
|
||||
data.push(level);
|
||||
index = data.length - 1;
|
||||
|
||||
var args = {level: level, lastLevel: lastLevel};
|
||||
var args = {level: level, lastLevel: lastLevel, originalEvent: event};
|
||||
|
||||
editor.fire('AddUndo', args);
|
||||
|
||||
if (index > 0) {
|
||||
editor.fire('change', args);
|
||||
editor.isNotDirty = false;
|
||||
editor.fire('change', args);
|
||||
}
|
||||
|
||||
return level;
|
||||
@@ -297,10 +325,6 @@ define("tinymce/UndoManager", [
|
||||
}
|
||||
|
||||
editor.setContent(level.content, {format: 'raw'});
|
||||
// undo后如果有pre, 那么要重新init下
|
||||
// life ace
|
||||
// LeaAce.undo(editor);
|
||||
|
||||
editor.selection.moveToBookmark(level.beforeBookmark);
|
||||
|
||||
editor.fire('undo', {level: level});
|
||||
@@ -375,9 +399,12 @@ define("tinymce/UndoManager", [
|
||||
transact: function(callback) {
|
||||
self.beforeChange();
|
||||
|
||||
lock = true;
|
||||
callback();
|
||||
lock = false;
|
||||
try {
|
||||
locks++;
|
||||
callback();
|
||||
} finally {
|
||||
locks--;
|
||||
}
|
||||
|
||||
self.add();
|
||||
}
|
||||
|
||||
Regular → Executable
+29
-8
@@ -49,6 +49,14 @@ define("tinymce/WindowManager", [
|
||||
|
||||
self.windows = windows;
|
||||
|
||||
editor.on('remove', function() {
|
||||
var i = windows.length;
|
||||
|
||||
while (i--) {
|
||||
windows[i].close();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Opens a new window.
|
||||
*
|
||||
@@ -58,15 +66,13 @@ define("tinymce/WindowManager", [
|
||||
* @option {String} file URL of the file to open in the window.
|
||||
* @option {Number} width Width in pixels.
|
||||
* @option {Number} height Height in pixels.
|
||||
* @option {Boolean} resizable Specifies whether the popup window is resizable or not.
|
||||
* @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
|
||||
* @option {String/bool} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
|
||||
* @option {Boolean} autoScroll Specifies whether the popup window can have scrollbars if required (i.e. content
|
||||
* larger than the popup size specified).
|
||||
*/
|
||||
self.open = function(args, params) {
|
||||
var win;
|
||||
|
||||
editor.editorManager.activeEditor = editor;
|
||||
editor.editorManager.setActive(editor);
|
||||
|
||||
args.title = args.title || ' ';
|
||||
|
||||
@@ -90,7 +96,6 @@ define("tinymce/WindowManager", [
|
||||
args.buttons = [
|
||||
{text: 'Ok', subtype: 'primary', onclick: function() {
|
||||
win.find('form')[0].submit();
|
||||
win.close();
|
||||
}},
|
||||
|
||||
{text: 'Cancel', onclick: function() {
|
||||
@@ -111,7 +116,9 @@ define("tinymce/WindowManager", [
|
||||
}
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
if (!windows.length) {
|
||||
editor.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle data
|
||||
@@ -132,9 +139,11 @@ define("tinymce/WindowManager", [
|
||||
win.params = params || {};
|
||||
|
||||
// Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
|
||||
editor.nodeChanged();
|
||||
if (windows.length === 1) {
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
return win.renderTo(document.body).reflow();
|
||||
return win.renderTo().reflow();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -153,6 +162,8 @@ define("tinymce/WindowManager", [
|
||||
MessageBox.alert(message, function() {
|
||||
if (callback) {
|
||||
callback.call(scope || this);
|
||||
} else {
|
||||
editor.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -216,5 +227,15 @@ define("tinymce/WindowManager", [
|
||||
getTopMostWindow().params = params;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the currently opened window objects.
|
||||
*
|
||||
* @method getWindows
|
||||
* @return {Array} Array of the currently opened windows.
|
||||
*/
|
||||
self.getWindows = function() {
|
||||
return windows;
|
||||
};
|
||||
};
|
||||
});
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
/**
|
||||
* BookmarkManager.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class handles selection bookmarks.
|
||||
*
|
||||
* @class tinymce.dom.BookmarkManager
|
||||
*/
|
||||
define("tinymce/dom/BookmarkManager", [
|
||||
"tinymce/Env",
|
||||
"tinymce/util/Tools"
|
||||
], function(Env, Tools) {
|
||||
/**
|
||||
* Constructs a new BookmarkManager instance for a specific selection instance.
|
||||
*
|
||||
* @constructor
|
||||
* @method BookmarkManager
|
||||
* @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
|
||||
*/
|
||||
function BookmarkManager(selection) {
|
||||
var dom = selection.dom;
|
||||
|
||||
/**
|
||||
* Returns a bookmark location for the current selection. This bookmark object
|
||||
* can then be used to restore the selection after some content modification to the document.
|
||||
*
|
||||
* @method getBookmark
|
||||
* @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
|
||||
* @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
|
||||
* @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
|
||||
* @example
|
||||
* // Stores a bookmark of the current selection
|
||||
* var bm = tinymce.activeEditor.selection.getBookmark();
|
||||
*
|
||||
* tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
|
||||
*
|
||||
* // Restore the selection bookmark
|
||||
* tinymce.activeEditor.selection.moveToBookmark(bm);
|
||||
*/
|
||||
this.getBookmark = function(type, normalized) {
|
||||
var rng, rng2, id, collapsed, name, element, chr = '', styles;
|
||||
|
||||
function findIndex(name, element) {
|
||||
var index = 0;
|
||||
|
||||
Tools.each(dom.select(name), function(node, i) {
|
||||
if (node == element) {
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
function normalizeTableCellSelection(rng) {
|
||||
function moveEndPoint(start) {
|
||||
var container, offset, childNodes, prefix = start ? 'start' : 'end';
|
||||
|
||||
container = rng[prefix + 'Container'];
|
||||
offset = rng[prefix + 'Offset'];
|
||||
|
||||
if (container.nodeType == 1 && container.nodeName == "TR") {
|
||||
childNodes = container.childNodes;
|
||||
container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
|
||||
if (container) {
|
||||
offset = start ? 0 : container.childNodes.length;
|
||||
rng['set' + (start ? 'Start' : 'End')](container, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveEndPoint(true);
|
||||
moveEndPoint();
|
||||
|
||||
return rng;
|
||||
}
|
||||
|
||||
function getLocation() {
|
||||
var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
|
||||
|
||||
function getPoint(rng, start) {
|
||||
var container = rng[start ? 'startContainer' : 'endContainer'],
|
||||
offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
|
||||
|
||||
if (container.nodeType == 3) {
|
||||
if (normalized) {
|
||||
for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
|
||||
offset += node.nodeValue.length;
|
||||
}
|
||||
}
|
||||
|
||||
point.push(offset);
|
||||
} else {
|
||||
childNodes = container.childNodes;
|
||||
|
||||
if (offset >= childNodes.length && childNodes.length) {
|
||||
after = 1;
|
||||
offset = Math.max(0, childNodes.length - 1);
|
||||
}
|
||||
|
||||
point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
|
||||
}
|
||||
|
||||
for (; container && container != root; container = container.parentNode) {
|
||||
point.push(dom.nodeIndex(container, normalized));
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
bookmark.start = getPoint(rng, true);
|
||||
|
||||
if (!selection.isCollapsed()) {
|
||||
bookmark.end = getPoint(rng);
|
||||
}
|
||||
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
if (type == 2) {
|
||||
element = selection.getNode();
|
||||
name = element ? element.nodeName : null;
|
||||
|
||||
if (name == 'IMG') {
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
|
||||
if (selection.tridentSel) {
|
||||
return selection.tridentSel.getBookmark(type);
|
||||
}
|
||||
|
||||
return getLocation();
|
||||
}
|
||||
|
||||
// Handle simple range
|
||||
if (type) {
|
||||
return {rng: selection.getRng()};
|
||||
}
|
||||
|
||||
rng = selection.getRng();
|
||||
id = dom.uniqueId();
|
||||
collapsed = selection.isCollapsed();
|
||||
styles = 'overflow:hidden;line-height:0px';
|
||||
|
||||
// Explorer method
|
||||
if (rng.duplicate || rng.item) {
|
||||
// Text selection
|
||||
if (!rng.item) {
|
||||
rng2 = rng.duplicate();
|
||||
|
||||
try {
|
||||
// Insert start marker
|
||||
rng.collapse();
|
||||
rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
|
||||
|
||||
// Insert end marker
|
||||
if (!collapsed) {
|
||||
rng2.collapse(false);
|
||||
|
||||
// Detect the empty space after block elements in IE and move the
|
||||
// end back one character <p></p>] becomes <p>]</p>
|
||||
rng.moveToElementText(rng2.parentElement());
|
||||
if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
|
||||
rng2.move('character', -1);
|
||||
}
|
||||
|
||||
rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
|
||||
}
|
||||
} catch (ex) {
|
||||
// IE might throw unspecified error so lets ignore it
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Control selection
|
||||
element = rng.item(0);
|
||||
name = element.nodeName;
|
||||
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
} else {
|
||||
element = selection.getNode();
|
||||
name = element.nodeName;
|
||||
if (name == 'IMG') {
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
|
||||
// W3C method
|
||||
rng2 = normalizeTableCellSelection(rng.cloneRange());
|
||||
|
||||
// Insert end marker
|
||||
if (!collapsed) {
|
||||
rng2.collapse(false);
|
||||
rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
|
||||
}
|
||||
|
||||
rng = normalizeTableCellSelection(rng);
|
||||
rng.collapse(true);
|
||||
rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
|
||||
}
|
||||
|
||||
selection.moveToBookmark({id: id, keep: 1});
|
||||
|
||||
return {id: id};
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the selection to the specified bookmark.
|
||||
*
|
||||
* @method moveToBookmark
|
||||
* @param {Object} bookmark Bookmark to restore selection from.
|
||||
* @return {Boolean} true/false if it was successful or not.
|
||||
* @example
|
||||
* // Stores a bookmark of the current selection
|
||||
* var bm = tinymce.activeEditor.selection.getBookmark();
|
||||
*
|
||||
* tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
|
||||
*
|
||||
* // Restore the selection bookmark
|
||||
* tinymce.activeEditor.selection.moveToBookmark(bm);
|
||||
*/
|
||||
this.moveToBookmark = function(bookmark) {
|
||||
var rng, root, startContainer, endContainer, startOffset, endOffset;
|
||||
|
||||
function setEndPoint(start) {
|
||||
var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
|
||||
|
||||
if (point) {
|
||||
offset = point[0];
|
||||
|
||||
// Find container node
|
||||
for (node = root, i = point.length - 1; i >= 1; i--) {
|
||||
children = node.childNodes;
|
||||
|
||||
if (point[i] > children.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
node = children[point[i]];
|
||||
}
|
||||
|
||||
// Move text offset to best suitable location
|
||||
if (node.nodeType === 3) {
|
||||
offset = Math.min(point[0], node.nodeValue.length);
|
||||
}
|
||||
|
||||
// Move element offset to best suitable location
|
||||
if (node.nodeType === 1) {
|
||||
offset = Math.min(point[0], node.childNodes.length);
|
||||
}
|
||||
|
||||
// Set offset within container node
|
||||
if (start) {
|
||||
rng.setStart(node, offset);
|
||||
} else {
|
||||
rng.setEnd(node, offset);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function restoreEndPoint(suffix) {
|
||||
var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
|
||||
|
||||
if (marker) {
|
||||
node = marker.parentNode;
|
||||
|
||||
if (suffix == 'start') {
|
||||
if (!keep) {
|
||||
idx = dom.nodeIndex(marker);
|
||||
} else {
|
||||
node = marker.firstChild;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
startContainer = endContainer = node;
|
||||
startOffset = endOffset = idx;
|
||||
} else {
|
||||
if (!keep) {
|
||||
idx = dom.nodeIndex(marker);
|
||||
} else {
|
||||
node = marker.firstChild;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
endContainer = node;
|
||||
endOffset = idx;
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
prev = marker.previousSibling;
|
||||
next = marker.nextSibling;
|
||||
|
||||
// Remove all marker text nodes
|
||||
Tools.each(Tools.grep(marker.childNodes), function(node) {
|
||||
if (node.nodeType == 3) {
|
||||
node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
|
||||
}
|
||||
});
|
||||
|
||||
// Remove marker but keep children if for example contents where inserted into the marker
|
||||
// Also remove duplicated instances of the marker for example by a
|
||||
// split operation or by WebKit auto split on paste feature
|
||||
while ((marker = dom.get(bookmark.id + '_' + suffix))) {
|
||||
dom.remove(marker, 1);
|
||||
}
|
||||
|
||||
// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
|
||||
// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
|
||||
// isn't worth the effort. Sorry, Opera but it's just a fact
|
||||
if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
|
||||
idx = prev.nodeValue.length;
|
||||
prev.appendData(next.nodeValue);
|
||||
dom.remove(next);
|
||||
|
||||
if (suffix == 'start') {
|
||||
startContainer = endContainer = prev;
|
||||
startOffset = endOffset = idx;
|
||||
} else {
|
||||
endContainer = prev;
|
||||
endOffset = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addBogus(node) {
|
||||
// Adds a bogus BR element for empty block elements
|
||||
if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
|
||||
node.innerHTML = '<br data-mce-bogus="1" />';
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
if (bookmark) {
|
||||
if (bookmark.start) {
|
||||
rng = dom.createRng();
|
||||
root = dom.getRoot();
|
||||
|
||||
if (selection.tridentSel) {
|
||||
return selection.tridentSel.moveToBookmark(bookmark);
|
||||
}
|
||||
|
||||
if (setEndPoint(true) && setEndPoint()) {
|
||||
selection.setRng(rng);
|
||||
}
|
||||
} else if (bookmark.id) {
|
||||
// Restore start/end points
|
||||
restoreEndPoint('start');
|
||||
restoreEndPoint('end');
|
||||
|
||||
if (startContainer) {
|
||||
rng = dom.createRng();
|
||||
rng.setStart(addBogus(startContainer), startOffset);
|
||||
rng.setEnd(addBogus(endContainer), endOffset);
|
||||
selection.setRng(rng);
|
||||
}
|
||||
} else if (bookmark.name) {
|
||||
selection.select(dom.select(bookmark.name)[bookmark.index]);
|
||||
} else if (bookmark.rng) {
|
||||
selection.setRng(bookmark.rng);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true/false if the specified node is a bookmark node or not.
|
||||
*
|
||||
* @static
|
||||
* @method isBookmarkNode
|
||||
* @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
|
||||
* @return {Boolean} true/false if the node is a bookmark node or not.
|
||||
*/
|
||||
BookmarkManager.isBookmarkNode = function(node) {
|
||||
return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
|
||||
};
|
||||
|
||||
return BookmarkManager;
|
||||
});
|
||||
Regular → Executable
+172
-57
@@ -22,21 +22,22 @@ define("tinymce/dom/ControlSelection", [
|
||||
], function(VK, Tools, Env) {
|
||||
return function(selection, editor) {
|
||||
var dom = editor.dom, each = Tools.each;
|
||||
var selectedElm, selectedElmGhost, resizeHandles, selectedHandle, lastMouseDownEvent;
|
||||
var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
|
||||
var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
|
||||
var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
|
||||
var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;
|
||||
|
||||
// Details about each resize handle how to scale etc
|
||||
resizeHandles = {
|
||||
// Name: x multiplier, y multiplier, delta size x, delta size y
|
||||
n: [0.5, 0, 0, -1],
|
||||
e: [1, 0.5, 1, 0],
|
||||
s: [0.5, 1, 0, 1],
|
||||
w: [0, 0.5, -1, 0],
|
||||
nw: [0, 0, -1, -1],
|
||||
ne: [1, 0, 1, -1],
|
||||
se: [1, 1, 1, 1],
|
||||
sw: [0, 1, -1, 1]
|
||||
n: [0.5, 0, 0, -1],
|
||||
e: [1, 0.5, 1, 0],
|
||||
s: [0.5, 1, 0, 1],
|
||||
w: [0, 0.5, -1, 0],
|
||||
nw: [0, 0, -1, -1],
|
||||
ne: [1, 0, 1, -1],
|
||||
se: [1, 1, 1, 1],
|
||||
sw: [0, 1, -1, 1]
|
||||
};
|
||||
|
||||
// Add CSS for resize handles, cloned element and selected
|
||||
@@ -63,27 +64,46 @@ define("tinymce/dom/ControlSelection", [
|
||||
'opacity: .5;' +
|
||||
'filter: alpha(opacity=50);' +
|
||||
'z-index: 10000' +
|
||||
'}' +
|
||||
rootClass + ' .mce-resize-helper {' +
|
||||
'background: #555;' +
|
||||
'background: rgba(0,0,0,0.75);' +
|
||||
'border-radius: 3px;' +
|
||||
'border: 1px;' +
|
||||
'color: white;' +
|
||||
'display: none;' +
|
||||
'font-family: sans-serif;' +
|
||||
'font-size: 12px;' +
|
||||
'white-space: nowrap;' +
|
||||
'line-height: 14px;' +
|
||||
'margin: 5px 10px;' +
|
||||
'padding: 5px;' +
|
||||
'position: absolute;' +
|
||||
'z-index: 10001' +
|
||||
'}'
|
||||
);
|
||||
|
||||
function isResizable(elm) {
|
||||
if (editor.settings.object_resizing === false) {
|
||||
var selector = editor.settings.object_resizing;
|
||||
|
||||
if (selector === false || Env.iOS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!/TABLE|IMG|DIV/.test(elm.nodeName)) {
|
||||
return false;
|
||||
if (typeof selector != 'string') {
|
||||
selector = 'table,img,div';
|
||||
}
|
||||
|
||||
if (elm.getAttribute('data-mce-resize') === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return editor.dom.is(elm, selector);
|
||||
}
|
||||
|
||||
function resizeGhostElement(e) {
|
||||
var deltaX, deltaY;
|
||||
var deltaX, deltaY, proportional;
|
||||
var resizeHelperX, resizeHelperY;
|
||||
|
||||
// Calc new width/height
|
||||
deltaX = e.screenX - startX;
|
||||
@@ -97,10 +117,21 @@ define("tinymce/dom/ControlSelection", [
|
||||
width = width < 5 ? 5 : width;
|
||||
height = height < 5 ? 5 : height;
|
||||
|
||||
// Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
|
||||
if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
|
||||
width = Math.round(height / ratio);
|
||||
height = Math.round(width * ratio);
|
||||
if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
|
||||
proportional = !VK.modifierPressed(e);
|
||||
} else {
|
||||
proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
|
||||
}
|
||||
|
||||
// Constrain proportions
|
||||
if (proportional) {
|
||||
if (abs(deltaX) > abs(deltaY)) {
|
||||
height = round(width * ratio);
|
||||
width = round(height / ratio);
|
||||
} else {
|
||||
width = round(height / ratio);
|
||||
height = round(width * ratio);
|
||||
}
|
||||
}
|
||||
|
||||
// Update ghost size
|
||||
@@ -109,6 +140,20 @@ define("tinymce/dom/ControlSelection", [
|
||||
height: height
|
||||
});
|
||||
|
||||
// Update resize helper position
|
||||
resizeHelperX = selectedHandle.startPos.x + deltaX;
|
||||
resizeHelperY = selectedHandle.startPos.y + deltaY;
|
||||
resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
|
||||
resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
|
||||
|
||||
dom.setStyles(resizeHelper, {
|
||||
left: resizeHelperX,
|
||||
top: resizeHelperY,
|
||||
display: 'block'
|
||||
});
|
||||
|
||||
resizeHelper.innerHTML = width + ' × ' + height;
|
||||
|
||||
// Update ghost X position if needed
|
||||
if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
|
||||
dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
|
||||
@@ -119,6 +164,18 @@ define("tinymce/dom/ControlSelection", [
|
||||
dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
|
||||
}
|
||||
|
||||
// Calculate how must overflow we got
|
||||
deltaX = rootElement.scrollWidth - startScrollWidth;
|
||||
deltaY = rootElement.scrollHeight - startScrollHeight;
|
||||
|
||||
// Re-position the resize helper based on the overflow
|
||||
if (deltaX + deltaY !== 0) {
|
||||
dom.setStyles(resizeHelper, {
|
||||
left: resizeHelperX - deltaX,
|
||||
top: resizeHelperY - deltaY
|
||||
});
|
||||
}
|
||||
|
||||
if (!resizeStarted) {
|
||||
editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
|
||||
resizeStarted = true;
|
||||
@@ -151,22 +208,26 @@ define("tinymce/dom/ControlSelection", [
|
||||
dom.unbind(rootDocument, 'mouseup', endGhostResize);
|
||||
}
|
||||
|
||||
// Remove ghost and update resize handle positions
|
||||
// Remove ghost/helper and update resize handle positions
|
||||
dom.remove(selectedElmGhost);
|
||||
dom.remove(resizeHelper);
|
||||
|
||||
if (!isIE || selectedElm.nodeName == "TABLE") {
|
||||
showResizeRect(selectedElm);
|
||||
}
|
||||
|
||||
editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
|
||||
dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
|
||||
var position, targetWidth, targetHeight, e, rect, offsetParent = editor.getBody();
|
||||
var position, targetWidth, targetHeight, e, rect;
|
||||
|
||||
unbindResizeHandleEvents();
|
||||
|
||||
// Get position and size of target
|
||||
position = dom.getPos(targetElm, offsetParent);
|
||||
position = dom.getPos(targetElm, rootElement);
|
||||
selectedElmX = position.x;
|
||||
selectedElmY = position.y;
|
||||
rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
|
||||
@@ -188,8 +249,6 @@ define("tinymce/dom/ControlSelection", [
|
||||
var handleElm, handlerContainerElm;
|
||||
|
||||
function startDrag(e) {
|
||||
resizeStarted = true;
|
||||
|
||||
startX = e.screenX;
|
||||
startY = e.screenY;
|
||||
startW = selectedElm.clientWidth;
|
||||
@@ -197,8 +256,17 @@ define("tinymce/dom/ControlSelection", [
|
||||
ratio = startH / startW;
|
||||
selectedHandle = handle;
|
||||
|
||||
handle.startPos = {
|
||||
x: targetWidth * handle[0] + selectedElmX,
|
||||
y: targetHeight * handle[1] + selectedElmY
|
||||
};
|
||||
|
||||
startScrollWidth = rootElement.scrollWidth;
|
||||
startScrollHeight = rootElement.scrollHeight;
|
||||
|
||||
selectedElmGhost = selectedElm.cloneNode(true);
|
||||
dom.addClass(selectedElmGhost, 'mce-clonedresizable');
|
||||
dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
|
||||
selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
|
||||
selectedElmGhost.unSelectabe = true;
|
||||
dom.setStyles(selectedElmGhost, {
|
||||
@@ -208,7 +276,7 @@ define("tinymce/dom/ControlSelection", [
|
||||
});
|
||||
|
||||
selectedElmGhost.removeAttribute('data-mce-selected');
|
||||
editor.getBody().appendChild(selectedElmGhost);
|
||||
rootElement.appendChild(selectedElmGhost);
|
||||
|
||||
dom.bind(editableDoc, 'mousemove', resizeGhostElement);
|
||||
dom.bind(editableDoc, 'mouseup', endGhostResize);
|
||||
@@ -217,6 +285,11 @@ define("tinymce/dom/ControlSelection", [
|
||||
dom.bind(rootDocument, 'mousemove', resizeGhostElement);
|
||||
dom.bind(rootDocument, 'mouseup', endGhostResize);
|
||||
}
|
||||
|
||||
resizeHelper = dom.add(rootElement, 'div', {
|
||||
'class': 'mce-resize-helper',
|
||||
'data-mce-bogus': 'all'
|
||||
}, startW + ' × ' + startH);
|
||||
}
|
||||
|
||||
if (mouseDownHandleName) {
|
||||
@@ -231,35 +304,34 @@ define("tinymce/dom/ControlSelection", [
|
||||
// Get existing or render resize handle
|
||||
handleElm = dom.get('mceResizeHandle' + name);
|
||||
if (!handleElm) {
|
||||
handlerContainerElm = editor.getBody();
|
||||
handlerContainerElm = rootElement;
|
||||
|
||||
handleElm = dom.add(handlerContainerElm, 'div', {
|
||||
id: 'mceResizeHandle' + name,
|
||||
'data-mce-bogus': true,
|
||||
'data-mce-bogus': 'all',
|
||||
'class': 'mce-resizehandle',
|
||||
contentEditable: false, // Hides IE move layer cursor
|
||||
unSelectabe: true,
|
||||
unselectable: true,
|
||||
style: 'cursor:' + name + '-resize; margin:0; padding:0'
|
||||
});
|
||||
|
||||
dom.bind(handleElm, 'mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
startDrag(e);
|
||||
});
|
||||
// Hides IE move layer cursor
|
||||
// If we set it on Chrome we get this wounderful bug: #6725
|
||||
if (Env.ie) {
|
||||
handleElm.contentEditable = false;
|
||||
}
|
||||
} else {
|
||||
dom.show(handleElm);
|
||||
}
|
||||
|
||||
/*
|
||||
var halfHandleW = handleElm.offsetWidth / 2;
|
||||
var halfHandleH = handleElm.offsetHeight / 2;
|
||||
if (!handle.elm) {
|
||||
dom.bind(handleElm, 'mousedown', function(e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
startDrag(e);
|
||||
});
|
||||
|
||||
// Position element
|
||||
dom.setStyles(handleElm, {
|
||||
left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)),
|
||||
top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH))
|
||||
});
|
||||
*/
|
||||
handle.elm = handleElm;
|
||||
}
|
||||
|
||||
// Position element
|
||||
dom.setStyles(handleElm, {
|
||||
@@ -277,6 +349,8 @@ define("tinymce/dom/ControlSelection", [
|
||||
function hideResizeRect() {
|
||||
var name, handleElm;
|
||||
|
||||
unbindResizeHandleEvents();
|
||||
|
||||
if (selectedElm) {
|
||||
selectedElm.removeAttribute('data-mce-selected');
|
||||
}
|
||||
@@ -291,14 +365,21 @@ define("tinymce/dom/ControlSelection", [
|
||||
}
|
||||
|
||||
function updateResizeRect(e) {
|
||||
var controlElm;
|
||||
var startElm, controlElm;
|
||||
|
||||
function isChildOrEqual(node, parent) {
|
||||
do {
|
||||
if (node === parent) {
|
||||
return true;
|
||||
}
|
||||
} while ((node = node.parentNode));
|
||||
if (node) {
|
||||
do {
|
||||
if (node === parent) {
|
||||
return true;
|
||||
}
|
||||
} while ((node = node.parentNode));
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore all events while resizing
|
||||
if (resizeStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
|
||||
@@ -307,13 +388,14 @@ define("tinymce/dom/ControlSelection", [
|
||||
});
|
||||
|
||||
controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
|
||||
controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr');
|
||||
controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
|
||||
|
||||
if (controlElm) {
|
||||
if (isChildOrEqual(controlElm, rootElement)) {
|
||||
disableGeckoResize();
|
||||
startElm = selection.getStart(true);
|
||||
|
||||
if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) {
|
||||
if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) {
|
||||
if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
|
||||
if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) {
|
||||
showResizeRect(controlElm);
|
||||
return;
|
||||
}
|
||||
@@ -349,7 +431,7 @@ define("tinymce/dom/ControlSelection", [
|
||||
cornerX = target.offsetWidth * corner[0];
|
||||
cornerY = target.offsetHeight * corner[1];
|
||||
|
||||
if (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) {
|
||||
if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
|
||||
selectedHandle = corner;
|
||||
break;
|
||||
}
|
||||
@@ -357,6 +439,11 @@ define("tinymce/dom/ControlSelection", [
|
||||
|
||||
// Remove native selection and let the magic begin
|
||||
resizeStarted = true;
|
||||
editor.fire('ObjectResizeStart', {
|
||||
target: selectedElm,
|
||||
width: selectedElm.clientWidth,
|
||||
height: selectedElm.clientHeight
|
||||
});
|
||||
editor.getDoc().selection.empty();
|
||||
showResizeRect(target, name, lastMouseDownEvent);
|
||||
}
|
||||
@@ -365,6 +452,7 @@ define("tinymce/dom/ControlSelection", [
|
||||
var target = e.srcElement;
|
||||
|
||||
if (target != selectedElm) {
|
||||
editor.fire('ObjectSelected', {target: target});
|
||||
detachResizeStartListener();
|
||||
|
||||
if (target.id.indexOf('mceResizeHandle') === 0) {
|
||||
@@ -384,6 +472,17 @@ define("tinymce/dom/ControlSelection", [
|
||||
detachEvent(selectedElm, 'resizestart', resizeNativeStart);
|
||||
}
|
||||
|
||||
function unbindResizeHandleEvents() {
|
||||
for (var name in resizeHandles) {
|
||||
var handle = resizeHandles[name];
|
||||
|
||||
if (handle.elm) {
|
||||
dom.unbind(handle.elm);
|
||||
delete handle.elm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function disableGeckoResize() {
|
||||
try {
|
||||
// Disable object resizing on Gecko
|
||||
@@ -421,7 +520,7 @@ define("tinymce/dom/ControlSelection", [
|
||||
}
|
||||
});
|
||||
|
||||
attachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
|
||||
attachEvent(rootElement, 'controlselect', nativeControlSelect);
|
||||
|
||||
editor.on('mousedown', function(e) {
|
||||
lastMouseDownEvent = e;
|
||||
@@ -434,21 +533,29 @@ define("tinymce/dom/ControlSelection", [
|
||||
editor.on('mouseup', function(e) {
|
||||
var nodeName = e.target.nodeName;
|
||||
|
||||
if (/^(TABLE|IMG|HR)$/.test(nodeName)) {
|
||||
if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) {
|
||||
editor.selection.select(e.target, nodeName == 'TABLE');
|
||||
editor.nodeChanged();
|
||||
}
|
||||
});
|
||||
|
||||
editor.dom.bind(editor.getBody(), 'mscontrolselect', function(e) {
|
||||
editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
|
||||
if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
|
||||
e.preventDefault();
|
||||
|
||||
// This moves the selection from being a control selection to a text like selection like in WebKit #6753
|
||||
// TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
|
||||
if (e.target.tagName == 'IMG') {
|
||||
window.setTimeout(function() {
|
||||
editor.selection.select(e.target);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect);
|
||||
editor.on('nodechange ResizeEditor', updateResizeRect);
|
||||
|
||||
// Update resize rect while typing in a table
|
||||
editor.on('keydown keyup', function(e) {
|
||||
@@ -457,20 +564,28 @@ define("tinymce/dom/ControlSelection", [
|
||||
}
|
||||
});
|
||||
|
||||
editor.on('hide', hideResizeRect);
|
||||
|
||||
// Hide rect on focusout since it would float on top of windows otherwise
|
||||
//editor.on('focusout', hideResizeRect);
|
||||
});
|
||||
|
||||
editor.on('remove', unbindResizeHandleEvents);
|
||||
|
||||
function destroy() {
|
||||
selectedElm = selectedElmGhost = null;
|
||||
|
||||
if (isIE) {
|
||||
detachResizeStartListener();
|
||||
detachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
|
||||
detachEvent(rootElement, 'controlselect', nativeControlSelect);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isResizable: isResizable,
|
||||
showResizeRect: showResizeRect,
|
||||
hideResizeRect: hideResizeRect,
|
||||
updateResizeRect: updateResizeRect,
|
||||
controlSelect: controlSelect,
|
||||
destroy: destroy
|
||||
};
|
||||
|
||||
Regular → Executable
+243
-486
File diff suppressed because it is too large.
Load diff
Regular → Executable
+1149
-305
File diff suppressed because it is too large.
Load diff
Executable
+117
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* ElementUtils.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility class for various element specific functions.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
define("tinymce/dom/ElementUtils", [
|
||||
"tinymce/dom/BookmarkManager",
|
||||
"tinymce/util/Tools"
|
||||
], function(BookmarkManager, Tools) {
|
||||
var each = Tools.each;
|
||||
|
||||
function ElementUtils(dom) {
|
||||
/**
|
||||
* Compares two nodes and checks if it's attributes and styles matches.
|
||||
* This doesn't compare classes as items since their order is significant.
|
||||
*
|
||||
* @method compare
|
||||
* @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.
|
||||
*/
|
||||
this.compare = function(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' && name !== 'data-mce-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 (typeof value == "undefined") {
|
||||
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 !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
|
||||
};
|
||||
}
|
||||
|
||||
return ElementUtils;
|
||||
});
|
||||
Regular → Executable
+19
-7
@@ -9,7 +9,13 @@
|
||||
*/
|
||||
|
||||
/*jshint loopfunc:true*/
|
||||
/*eslint no-loop-func:0 */
|
||||
|
||||
/**
|
||||
* This class wraps the browsers native event logic with more convenient methods.
|
||||
*
|
||||
* @class tinymce.dom.EventUtils
|
||||
*/
|
||||
define("tinymce/dom/EventUtils", [], function() {
|
||||
"use strict";
|
||||
|
||||
@@ -74,11 +80,11 @@ define("tinymce/dom/EventUtils", [], function() {
|
||||
var doc = eventDoc.documentElement;
|
||||
var body = eventDoc.body;
|
||||
|
||||
event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
|
||||
( doc && doc.clientLeft || body && body.clientLeft || 0);
|
||||
event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
|
||||
(doc && doc.clientLeft || body && body.clientLeft || 0);
|
||||
|
||||
event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0 ) -
|
||||
( doc && doc.clientTop || body && body.clientTop || 0);
|
||||
event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
|
||||
(doc && doc.clientTop || body && body.clientTop || 0);
|
||||
}
|
||||
|
||||
// Add preventDefault method
|
||||
@@ -106,7 +112,7 @@ define("tinymce/dom/EventUtils", [], function() {
|
||||
} else {
|
||||
originalEvent.cancelBubble = true; // IE
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add stopImmediatePropagation
|
||||
@@ -146,7 +152,9 @@ define("tinymce/dom/EventUtils", [], function() {
|
||||
}
|
||||
|
||||
function waitForDomLoaded() {
|
||||
if (doc.readyState === "complete") {
|
||||
// Check complete or interactive state if there is a body
|
||||
// element on some iframes IE 8 will produce a null body
|
||||
if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
|
||||
removeEvent(doc, "readystatechange", waitForDomLoaded);
|
||||
readyHandler();
|
||||
}
|
||||
@@ -177,7 +185,7 @@ define("tinymce/dom/EventUtils", [], function() {
|
||||
addEvent(doc, "readystatechange", waitForDomLoaded);
|
||||
|
||||
// Wait until we can scroll, when we can the DOM is initialized
|
||||
if (doc.documentElement.doScroll && win === win.top) {
|
||||
if (doc.documentElement.doScroll && win.self === win.top) {
|
||||
tryScroll();
|
||||
}
|
||||
}
|
||||
@@ -334,6 +342,7 @@ define("tinymce/dom/EventUtils", [], function() {
|
||||
events[id][name] = callbackList = [{func: callback, scope: scope}];
|
||||
callbackList.fakeName = fakeName;
|
||||
callbackList.capture = capture;
|
||||
//callbackList.callback = callback;
|
||||
|
||||
// Add the nativeHandler to the callback list so that we can later unbind it
|
||||
callbackList.nativeHandler = nativeHandler;
|
||||
@@ -398,10 +407,13 @@ define("tinymce/dom/EventUtils", [], function() {
|
||||
while (ci--) {
|
||||
if (callbackList[ci].func === callback) {
|
||||
var nativeHandler = callbackList.nativeHandler;
|
||||
var fakeName = callbackList.fakeName, capture = callbackList.capture;
|
||||
|
||||
// Clone callbackList since unbind inside a callback would otherwise break the handlers loop
|
||||
callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
|
||||
callbackList.nativeHandler = nativeHandler;
|
||||
callbackList.fakeName = fakeName;
|
||||
callbackList.capture = capture;
|
||||
|
||||
eventMap[name] = callbackList;
|
||||
}
|
||||
|
||||
Regular → Executable
+65
-66
@@ -13,7 +13,7 @@ define("tinymce/dom/Range", [
|
||||
], function(Tools) {
|
||||
// Range constructor
|
||||
function Range(dom) {
|
||||
var t = this,
|
||||
var self = this,
|
||||
doc = dom.doc,
|
||||
EXTRACT = 0,
|
||||
CLONE = 1,
|
||||
@@ -57,14 +57,14 @@ define("tinymce/dom/Range", [
|
||||
|
||||
function collapse(ts) {
|
||||
if (ts) {
|
||||
t[END_CONTAINER] = t[START_CONTAINER];
|
||||
t[END_OFFSET] = t[START_OFFSET];
|
||||
self[END_CONTAINER] = self[START_CONTAINER];
|
||||
self[END_OFFSET] = self[START_OFFSET];
|
||||
} else {
|
||||
t[START_CONTAINER] = t[END_CONTAINER];
|
||||
t[START_OFFSET] = t[END_OFFSET];
|
||||
self[START_CONTAINER] = self[END_CONTAINER];
|
||||
self[START_OFFSET] = self[END_OFFSET];
|
||||
}
|
||||
|
||||
t.collapsed = TRUE;
|
||||
self.collapsed = TRUE;
|
||||
}
|
||||
|
||||
function selectNode(n) {
|
||||
@@ -78,7 +78,7 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
function compareBoundaryPoints(h, r) {
|
||||
var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
|
||||
var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
|
||||
rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
|
||||
|
||||
// Check START_TO_START
|
||||
@@ -150,21 +150,21 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
function surroundContents(n) {
|
||||
var f = t.extractContents();
|
||||
var f = self.extractContents();
|
||||
|
||||
t.insertNode(n);
|
||||
self.insertNode(n);
|
||||
n.appendChild(f);
|
||||
t.selectNode(n);
|
||||
self.selectNode(n);
|
||||
}
|
||||
|
||||
function cloneRange() {
|
||||
return extend(new Range(dom), {
|
||||
startContainer: t[START_CONTAINER],
|
||||
startOffset: t[START_OFFSET],
|
||||
endContainer: t[END_CONTAINER],
|
||||
endOffset: t[END_OFFSET],
|
||||
collapsed: t.collapsed,
|
||||
commonAncestorContainer: t.commonAncestorContainer
|
||||
startContainer: self[START_CONTAINER],
|
||||
startOffset: self[START_OFFSET],
|
||||
endContainer: self[END_CONTAINER],
|
||||
endOffset: self[END_OFFSET],
|
||||
collapsed: self.collapsed,
|
||||
commonAncestorContainer: self.commonAncestorContainer
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
function _isCollapsed() {
|
||||
return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
|
||||
return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
|
||||
}
|
||||
|
||||
function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
|
||||
@@ -311,23 +311,23 @@ define("tinymce/dom/Range", [
|
||||
var ec, sc;
|
||||
|
||||
if (st) {
|
||||
t[START_CONTAINER] = n;
|
||||
t[START_OFFSET] = o;
|
||||
self[START_CONTAINER] = n;
|
||||
self[START_OFFSET] = o;
|
||||
} else {
|
||||
t[END_CONTAINER] = n;
|
||||
t[END_OFFSET] = o;
|
||||
self[END_CONTAINER] = n;
|
||||
self[END_OFFSET] = o;
|
||||
}
|
||||
|
||||
// If one boundary-point of a Range is set to have a root container
|
||||
// other than the current one for the Range, the Range is collapsed to
|
||||
// the new position. This enforces the restriction that both boundary-
|
||||
// points of a Range must have the same root container.
|
||||
ec = t[END_CONTAINER];
|
||||
ec = self[END_CONTAINER];
|
||||
while (ec.parentNode) {
|
||||
ec = ec.parentNode;
|
||||
}
|
||||
|
||||
sc = t[START_CONTAINER];
|
||||
sc = self[START_CONTAINER];
|
||||
while (sc.parentNode) {
|
||||
sc = sc.parentNode;
|
||||
}
|
||||
@@ -337,34 +337,34 @@ define("tinymce/dom/Range", [
|
||||
// end position. To enforce this restriction, if the start is set to
|
||||
// be at a position after the end, the Range is collapsed to that
|
||||
// position.
|
||||
if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) {
|
||||
t.collapse(st);
|
||||
if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
|
||||
self.collapse(st);
|
||||
}
|
||||
} else {
|
||||
t.collapse(st);
|
||||
self.collapse(st);
|
||||
}
|
||||
|
||||
t.collapsed = _isCollapsed();
|
||||
t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
|
||||
self.collapsed = _isCollapsed();
|
||||
self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
|
||||
}
|
||||
|
||||
function _traverse(how) {
|
||||
var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
|
||||
|
||||
if (t[START_CONTAINER] == t[END_CONTAINER]) {
|
||||
if (self[START_CONTAINER] == self[END_CONTAINER]) {
|
||||
return _traverseSameContainer(how);
|
||||
}
|
||||
|
||||
for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
|
||||
if (p == t[START_CONTAINER]) {
|
||||
for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
|
||||
if (p == self[START_CONTAINER]) {
|
||||
return _traverseCommonStartContainer(c, how);
|
||||
}
|
||||
|
||||
++endContainerDepth;
|
||||
}
|
||||
|
||||
for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
|
||||
if (p == t[END_CONTAINER]) {
|
||||
for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
|
||||
if (p == self[END_CONTAINER]) {
|
||||
return _traverseCommonEndContainer(c, how);
|
||||
}
|
||||
|
||||
@@ -373,13 +373,13 @@ define("tinymce/dom/Range", [
|
||||
|
||||
depthDiff = startContainerDepth - endContainerDepth;
|
||||
|
||||
startNode = t[START_CONTAINER];
|
||||
startNode = self[START_CONTAINER];
|
||||
while (depthDiff > 0) {
|
||||
startNode = startNode.parentNode;
|
||||
depthDiff--;
|
||||
}
|
||||
|
||||
endNode = t[END_CONTAINER];
|
||||
endNode = self[END_CONTAINER];
|
||||
while (depthDiff < 0) {
|
||||
endNode = endNode.parentNode;
|
||||
depthDiff++;
|
||||
@@ -394,7 +394,7 @@ define("tinymce/dom/Range", [
|
||||
return _traverseCommonAncestors(startNode, endNode, how);
|
||||
}
|
||||
|
||||
function _traverseSameContainer(how) {
|
||||
function _traverseSameContainer(how) {
|
||||
var frag, s, sub, n, cnt, sibling, xferNode, start, len;
|
||||
|
||||
if (how != DELETE) {
|
||||
@@ -402,21 +402,21 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
// If selection is empty, just return the fragment
|
||||
if (t[START_OFFSET] == t[END_OFFSET]) {
|
||||
if (self[START_OFFSET] == self[END_OFFSET]) {
|
||||
return frag;
|
||||
}
|
||||
|
||||
// Text node needs special case handling
|
||||
if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
|
||||
if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
|
||||
// get the substring
|
||||
s = t[START_CONTAINER].nodeValue;
|
||||
sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
|
||||
s = self[START_CONTAINER].nodeValue;
|
||||
sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
|
||||
|
||||
// set the original text node to its new value
|
||||
if (how != CLONE) {
|
||||
n = t[START_CONTAINER];
|
||||
start = t[START_OFFSET];
|
||||
len = t[END_OFFSET] - t[START_OFFSET];
|
||||
n = self[START_CONTAINER];
|
||||
start = self[START_OFFSET];
|
||||
len = self[END_OFFSET] - self[START_OFFSET];
|
||||
|
||||
if (start === 0 && len >= n.nodeValue.length - 1) {
|
||||
n.parentNode.removeChild(n);
|
||||
@@ -425,7 +425,7 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
// Nothing is partially selected, so collapse to start point
|
||||
t.collapse(TRUE);
|
||||
self.collapse(TRUE);
|
||||
}
|
||||
|
||||
if (how == DELETE) {
|
||||
@@ -440,8 +440,8 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
// Copy nodes between the start/end offsets.
|
||||
n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
|
||||
cnt = t[END_OFFSET] - t[START_OFFSET];
|
||||
n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
|
||||
cnt = self[END_OFFSET] - self[START_OFFSET];
|
||||
|
||||
while (n && cnt > 0) {
|
||||
sibling = n.nextSibling;
|
||||
@@ -457,7 +457,7 @@ define("tinymce/dom/Range", [
|
||||
|
||||
// Nothing is partially selected, so collapse to start point
|
||||
if (how != CLONE) {
|
||||
t.collapse(TRUE);
|
||||
self.collapse(TRUE);
|
||||
}
|
||||
|
||||
return frag;
|
||||
@@ -477,14 +477,14 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
endIdx = nodeIndex(endAncestor);
|
||||
cnt = endIdx - t[START_OFFSET];
|
||||
cnt = endIdx - self[START_OFFSET];
|
||||
|
||||
if (cnt <= 0) {
|
||||
// Collapse to just before the endAncestor, which
|
||||
// is partially selected.
|
||||
if (how != CLONE) {
|
||||
t.setEndBefore(endAncestor);
|
||||
t.collapse(FALSE);
|
||||
self.setEndBefore(endAncestor);
|
||||
self.collapse(FALSE);
|
||||
}
|
||||
|
||||
return frag;
|
||||
@@ -506,8 +506,8 @@ define("tinymce/dom/Range", [
|
||||
// Collapse to just before the endAncestor, which
|
||||
// is partially selected.
|
||||
if (how != CLONE) {
|
||||
t.setEndBefore(endAncestor);
|
||||
t.collapse(FALSE);
|
||||
self.setEndBefore(endAncestor);
|
||||
self.collapse(FALSE);
|
||||
}
|
||||
|
||||
return frag;
|
||||
@@ -528,7 +528,7 @@ define("tinymce/dom/Range", [
|
||||
startIdx = nodeIndex(startAncestor);
|
||||
++startIdx; // Because we already traversed it
|
||||
|
||||
cnt = t[END_OFFSET] - startIdx;
|
||||
cnt = self[END_OFFSET] - startIdx;
|
||||
n = startAncestor.nextSibling;
|
||||
while (n && cnt > 0) {
|
||||
sibling = n.nextSibling;
|
||||
@@ -543,15 +543,15 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
if (how != CLONE) {
|
||||
t.setStartAfter(startAncestor);
|
||||
t.collapse(TRUE);
|
||||
self.setStartAfter(startAncestor);
|
||||
self.collapse(TRUE);
|
||||
}
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
function _traverseCommonAncestors(startAncestor, endAncestor, how) {
|
||||
var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
|
||||
var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
|
||||
|
||||
if (how != DELETE) {
|
||||
frag = createDocumentFragment();
|
||||
@@ -562,7 +562,6 @@ define("tinymce/dom/Range", [
|
||||
frag.appendChild(n);
|
||||
}
|
||||
|
||||
commonParent = startAncestor.parentNode;
|
||||
startOffset = nodeIndex(startAncestor);
|
||||
endOffset = nodeIndex(endAncestor);
|
||||
++startOffset;
|
||||
@@ -589,16 +588,16 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
if (how != CLONE) {
|
||||
t.setStartAfter(startAncestor);
|
||||
t.collapse(TRUE);
|
||||
self.setStartAfter(startAncestor);
|
||||
self.collapse(TRUE);
|
||||
}
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
function _traverseRightBoundary(root, how) {
|
||||
var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent;
|
||||
var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
|
||||
var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
|
||||
var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
|
||||
|
||||
if (next == root) {
|
||||
return _traverseNode(next, isFullySelected, FALSE, how);
|
||||
@@ -638,7 +637,7 @@ define("tinymce/dom/Range", [
|
||||
}
|
||||
|
||||
function _traverseLeftBoundary(root, how) {
|
||||
var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER];
|
||||
var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
|
||||
var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
|
||||
|
||||
if (next == root) {
|
||||
@@ -689,11 +688,11 @@ define("tinymce/dom/Range", [
|
||||
txtValue = n.nodeValue;
|
||||
|
||||
if (isLeft) {
|
||||
offset = t[START_OFFSET];
|
||||
offset = self[START_OFFSET];
|
||||
newNodeValue = txtValue.substring(offset);
|
||||
oldNodeValue = txtValue.substring(0, offset);
|
||||
} else {
|
||||
offset = t[END_OFFSET];
|
||||
offset = self[END_OFFSET];
|
||||
newNodeValue = txtValue.substring(0, offset);
|
||||
oldNodeValue = txtValue.substring(offset);
|
||||
}
|
||||
@@ -731,7 +730,7 @@ define("tinymce/dom/Range", [
|
||||
return dom.create('body', null, cloneContents()).outerText;
|
||||
}
|
||||
|
||||
extend(t, {
|
||||
extend(self, {
|
||||
// Inital states
|
||||
startContainer: doc,
|
||||
startOffset: 0,
|
||||
@@ -766,7 +765,7 @@ define("tinymce/dom/Range", [
|
||||
toStringIE: toStringIE
|
||||
});
|
||||
|
||||
return t;
|
||||
return self;
|
||||
}
|
||||
|
||||
// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
|
||||
|
||||
Regular → Executable
+271
-7
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Range.js
|
||||
* RangeUtils.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
@@ -9,16 +9,30 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* RangeUtils
|
||||
* This class contains a few utility methods for ranges.
|
||||
*
|
||||
* @class tinymce.dom.RangeUtils
|
||||
* @private
|
||||
*/
|
||||
define("tinymce/dom/RangeUtils", [
|
||||
"tinymce/util/Tools"
|
||||
], function(Tools) {
|
||||
"tinymce/util/Tools",
|
||||
"tinymce/dom/TreeWalker"
|
||||
], function(Tools, TreeWalker) {
|
||||
var each = Tools.each;
|
||||
|
||||
function getEndChild(container, index) {
|
||||
var childNodes = container.childNodes;
|
||||
|
||||
index--;
|
||||
|
||||
if (index > childNodes.length - 1) {
|
||||
index = childNodes.length - 1;
|
||||
} else if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
return childNodes[index] || container;
|
||||
}
|
||||
|
||||
function RangeUtils(dom) {
|
||||
/**
|
||||
* Walks the specified range like object and executes the callback for each sibling collection it finds.
|
||||
@@ -104,7 +118,7 @@ define("tinymce/dom/RangeUtils", [
|
||||
}
|
||||
|
||||
node = node.parentNode;
|
||||
} while(node);
|
||||
} while (node);
|
||||
}
|
||||
|
||||
function walkBoundary(start_node, end_node, next) {
|
||||
@@ -131,7 +145,7 @@ define("tinymce/dom/RangeUtils", [
|
||||
|
||||
// If index based end position then resolve it
|
||||
if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
|
||||
endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
|
||||
endContainer = getEndChild(endContainer, endOffset);
|
||||
}
|
||||
|
||||
// Same container
|
||||
@@ -239,6 +253,209 @@ define("tinymce/dom/RangeUtils", [
|
||||
endOffset: endOffset
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes the specified range by finding the closest best suitable caret location.
|
||||
*
|
||||
* @private
|
||||
* @param {Range} rng Range to normalize.
|
||||
* @return {Boolean} True/false if the specified range was normalized or not.
|
||||
*/
|
||||
this.normalize = function(rng) {
|
||||
var normalized, collapsed;
|
||||
|
||||
function normalizeEndPoint(start) {
|
||||
var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
|
||||
var directionLeft, isAfterNode;
|
||||
|
||||
function hasBrBeforeAfter(node, left) {
|
||||
var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
|
||||
|
||||
while ((node = walker[left ? 'prev' : 'next']())) {
|
||||
if (node.nodeName === "BR") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPrevNode(node, name) {
|
||||
return node.previousSibling && node.previousSibling.nodeName == name;
|
||||
}
|
||||
|
||||
// Walks the dom left/right to find a suitable text node to move the endpoint into
|
||||
// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
|
||||
function findTextNodeRelative(left, startNode) {
|
||||
var walker, lastInlineElement, parentBlockContainer;
|
||||
|
||||
startNode = startNode || container;
|
||||
parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
|
||||
|
||||
// Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
|
||||
// This: <p><br>|</p> becomes <p>|<br></p>
|
||||
if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
|
||||
container = startNode.parentNode;
|
||||
offset = dom.nodeIndex(startNode);
|
||||
normalized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Walk left until we hit a text node we can move to or a block/br/img
|
||||
walker = new TreeWalker(startNode, parentBlockContainer);
|
||||
while ((node = walker[left ? 'prev' : 'next']())) {
|
||||
// Break if we hit a non content editable node
|
||||
if (dom.getContentEditableParent(node) === "false") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Found text node that has a length
|
||||
if (node.nodeType === 3 && node.nodeValue.length > 0) {
|
||||
container = node;
|
||||
offset = left ? node.nodeValue.length : 0;
|
||||
normalized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Break if we find a block or a BR/IMG/INPUT etc
|
||||
if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastInlineElement = node;
|
||||
}
|
||||
|
||||
// Only fetch the last inline element when in caret mode for now
|
||||
if (collapsed && lastInlineElement) {
|
||||
container = lastInlineElement;
|
||||
normalized = true;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
container = rng[(start ? 'start' : 'end') + 'Container'];
|
||||
offset = rng[(start ? 'start' : 'end') + 'Offset'];
|
||||
isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
|
||||
nonEmptyElementsMap = dom.schema.getNonEmptyElements();
|
||||
directionLeft = start;
|
||||
|
||||
if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
|
||||
directionLeft = false;
|
||||
}
|
||||
|
||||
// If the container is a document move it to the body element
|
||||
if (container.nodeType === 9) {
|
||||
container = dom.getRoot();
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
// If the container is body try move it into the closest text node or position
|
||||
if (container === body) {
|
||||
// If start is before/after a image, table etc
|
||||
if (directionLeft) {
|
||||
node = container.childNodes[offset > 0 ? offset - 1 : 0];
|
||||
if (node) {
|
||||
if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the index
|
||||
if (container.hasChildNodes()) {
|
||||
offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
|
||||
container = container.childNodes[offset];
|
||||
offset = 0;
|
||||
|
||||
// Don't walk into elements that doesn't have any child nodes like a IMG
|
||||
if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
|
||||
// Walk the DOM to find a text node to place the caret at or a BR
|
||||
node = container;
|
||||
walker = new TreeWalker(container, body);
|
||||
|
||||
do {
|
||||
// Found a text node use that position
|
||||
if (node.nodeType === 3 && node.nodeValue.length > 0) {
|
||||
offset = directionLeft ? 0 : node.nodeValue.length;
|
||||
container = node;
|
||||
normalized = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Found a BR/IMG element that we can place the caret before
|
||||
if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
||||
offset = dom.nodeIndex(node);
|
||||
container = node.parentNode;
|
||||
|
||||
// Put caret after image when moving the end point
|
||||
if (node.nodeName == "IMG" && !directionLeft) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
normalized = true;
|
||||
break;
|
||||
}
|
||||
} while ((node = (directionLeft ? walker.next() : walker.prev())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lean the caret to the left if possible
|
||||
if (collapsed) {
|
||||
// So this: <b>x</b><i>|x</i>
|
||||
// Becomes: <b>x|</b><i>x</i>
|
||||
// Seems that only gecko has issues with this
|
||||
if (container.nodeType === 3 && offset === 0) {
|
||||
findTextNodeRelative(true);
|
||||
}
|
||||
|
||||
// Lean left into empty inline elements when the caret is before a BR
|
||||
// So this: <i><b></b><i>|<br></i>
|
||||
// Becomes: <i><b>|</b><i><br></i>
|
||||
// Seems that only gecko has issues with this.
|
||||
// Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
|
||||
if (container.nodeType === 1) {
|
||||
node = container.childNodes[offset];
|
||||
|
||||
// Offset is after the containers last child
|
||||
// then use the previous child for normalization
|
||||
if (!node) {
|
||||
node = container.childNodes[offset - 1];
|
||||
}
|
||||
|
||||
if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
|
||||
!hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
|
||||
findTextNodeRelative(true, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lean the start of the selection right if possible
|
||||
// So this: x[<b>x]</b>
|
||||
// Becomes: x<b>[x]</b>
|
||||
if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
|
||||
findTextNodeRelative(false);
|
||||
}
|
||||
|
||||
// Set endpoint if it was normalized
|
||||
if (normalized) {
|
||||
rng['set' + (start ? 'Start' : 'End')](container, offset);
|
||||
}
|
||||
}
|
||||
|
||||
collapsed = rng.collapsed;
|
||||
|
||||
normalizeEndPoint(true);
|
||||
|
||||
if (!collapsed) {
|
||||
normalizeEndPoint();
|
||||
}
|
||||
|
||||
// If it was collapsed then make sure it still is
|
||||
if (normalized && collapsed) {
|
||||
rng.collapse(true);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,5 +489,52 @@ define("tinymce/dom/RangeUtils", [
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the caret range for the given x/y location.
|
||||
*
|
||||
* @static
|
||||
* @method getCaretRangeFromPoint
|
||||
* @param {Number} x X coordinate for range
|
||||
* @param {Number} y Y coordinate for range
|
||||
* @param {Document} doc Document that x/y are relative to
|
||||
* @returns {Range} caret range
|
||||
*/
|
||||
RangeUtils.getCaretRangeFromPoint = function(x, y, doc) {
|
||||
var rng, point;
|
||||
|
||||
if (doc.caretPositionFromPoint) {
|
||||
point = doc.caretPositionFromPoint(x, y);
|
||||
rng = doc.createRange();
|
||||
rng.setStart(point.offsetNode, point.offset);
|
||||
rng.collapse(true);
|
||||
} else if (doc.caretRangeFromPoint) {
|
||||
rng = doc.caretRangeFromPoint(x, y);
|
||||
} else if (doc.body.createTextRange) {
|
||||
rng = doc.body.createTextRange();
|
||||
|
||||
try {
|
||||
rng.moveToPoint(x, y);
|
||||
rng.collapse(true);
|
||||
} catch (ex) {
|
||||
// Append to top or bottom depending on drop location
|
||||
rng.collapse(y < doc.body.clientHeight);
|
||||
}
|
||||
}
|
||||
|
||||
return rng;
|
||||
};
|
||||
|
||||
RangeUtils.getNode = function(container, offset) {
|
||||
if (container.nodeType == 1 && container.hasChildNodes()) {
|
||||
if (offset >= container.childNodes.length) {
|
||||
offset = container.childNodes.length - 1;
|
||||
}
|
||||
|
||||
container = container.childNodes[offset];
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
return RangeUtils;
|
||||
});
|
||||
Regular → Executable
+4
-2
@@ -76,8 +76,10 @@ define("tinymce/dom/ScriptLoader", [
|
||||
}
|
||||
|
||||
function error() {
|
||||
/*eslint no-console:0 */
|
||||
|
||||
// Report the error so it's easier for people to spot loading errors
|
||||
if (typeof(console) !== "undefined" && console.log) {
|
||||
if (typeof console !== "undefined" && console.log) {
|
||||
console.log("Failed to load: " + url);
|
||||
}
|
||||
|
||||
@@ -93,7 +95,7 @@ define("tinymce/dom/ScriptLoader", [
|
||||
elm = document.createElement('script');
|
||||
elm.id = id;
|
||||
elm.type = 'text/javascript';
|
||||
elm.src = url;
|
||||
elm.src = Tools._addCacheSuffix(url);
|
||||
|
||||
// Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
|
||||
if ("onreadystatechange" in elm) {
|
||||
|
||||
Regular → Executable
+150
-565
@@ -21,11 +21,13 @@ define("tinymce/dom/Selection", [
|
||||
"tinymce/dom/TreeWalker",
|
||||
"tinymce/dom/TridentSelection",
|
||||
"tinymce/dom/ControlSelection",
|
||||
"tinymce/dom/RangeUtils",
|
||||
"tinymce/dom/BookmarkManager",
|
||||
"tinymce/Env",
|
||||
"tinymce/util/Tools"
|
||||
], function(TreeWalker, TridentSelection, ControlSelection, Env, Tools) {
|
||||
var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
|
||||
var isIE = Env.ie, isOpera = Env.opera;
|
||||
], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) {
|
||||
var each = Tools.each, trim = Tools.trim;
|
||||
var isIE = Env.ie;
|
||||
|
||||
/**
|
||||
* Constructs a new selection instance.
|
||||
@@ -43,7 +45,7 @@ define("tinymce/dom/Selection", [
|
||||
self.win = win;
|
||||
self.serializer = serializer;
|
||||
self.editor = editor;
|
||||
|
||||
self.bookmarkManager = new BookmarkManager(self);
|
||||
self.controlSelection = new ControlSelection(self, editor);
|
||||
|
||||
// No W3C Range support
|
||||
@@ -55,17 +57,24 @@ define("tinymce/dom/Selection", [
|
||||
Selection.prototype = {
|
||||
/**
|
||||
* Move the selection cursor range to the specified node and offset.
|
||||
* If there is no node specified it will move it to the first suitable location within the body.
|
||||
*
|
||||
* @method setCursorLocation
|
||||
* @param {Node} node Node to put the cursor in.
|
||||
* @param {Number} offset Offset from the start of the node to put the cursor at.
|
||||
* @param {Node} node Optional node to put the cursor in.
|
||||
* @param {Number} offset Optional offset from the start of the node to put the cursor at.
|
||||
*/
|
||||
setCursorLocation: function(node, offset) {
|
||||
var self = this, rng = self.dom.createRng();
|
||||
rng.setStart(node, offset);
|
||||
rng.setEnd(node, offset);
|
||||
self.setRng(rng);
|
||||
self.collapse(false);
|
||||
|
||||
if (!node) {
|
||||
self._moveEndPoint(rng, self.editor.getBody(), true);
|
||||
self.setRng(rng);
|
||||
} else {
|
||||
rng.setStart(node, offset);
|
||||
rng.setEnd(node, offset);
|
||||
self.setRng(rng);
|
||||
self.collapse(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -230,9 +239,10 @@ define("tinymce/dom/Selection", [
|
||||
* node the parent element will be returned.
|
||||
*
|
||||
* @method getStart
|
||||
* @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
|
||||
* @return {Element} Start element of selection range.
|
||||
*/
|
||||
getStart: function() {
|
||||
getStart: function(real) {
|
||||
var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
|
||||
|
||||
if (rng.duplicate || rng.item) {
|
||||
@@ -264,7 +274,9 @@ define("tinymce/dom/Selection", [
|
||||
startElement = rng.startContainer;
|
||||
|
||||
if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
|
||||
startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
|
||||
if (!real || !rng.collapsed) {
|
||||
startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
|
||||
}
|
||||
}
|
||||
|
||||
if (startElement && startElement.nodeType == 3) {
|
||||
@@ -280,9 +292,10 @@ define("tinymce/dom/Selection", [
|
||||
* node the parent element will be returned.
|
||||
*
|
||||
* @method getEnd
|
||||
* @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
|
||||
* @return {Element} End element of selection range.
|
||||
*/
|
||||
getEnd: function() {
|
||||
getEnd: function(real) {
|
||||
var self = this, rng = self.getRng(), endElement, endOffset;
|
||||
|
||||
if (rng.duplicate || rng.item) {
|
||||
@@ -307,7 +320,9 @@ define("tinymce/dom/Selection", [
|
||||
endOffset = rng.endOffset;
|
||||
|
||||
if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
|
||||
endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
|
||||
if (!real || !rng.collapsed) {
|
||||
endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
|
||||
}
|
||||
}
|
||||
|
||||
if (endElement && endElement.nodeType == 3) {
|
||||
@@ -336,169 +351,7 @@ define("tinymce/dom/Selection", [
|
||||
* tinymce.activeEditor.selection.moveToBookmark(bm);
|
||||
*/
|
||||
getBookmark: function(type, normalized) {
|
||||
var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, chr = '', styles;
|
||||
|
||||
function findIndex(name, element) {
|
||||
var index = 0;
|
||||
|
||||
each(dom.select(name), function(node, i) {
|
||||
if (node == element) {
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
function normalizeTableCellSelection(rng) {
|
||||
function moveEndPoint(start) {
|
||||
var container, offset, childNodes, prefix = start ? 'start' : 'end';
|
||||
|
||||
container = rng[prefix + 'Container'];
|
||||
offset = rng[prefix + 'Offset'];
|
||||
|
||||
if (container.nodeType == 1 && container.nodeName == "TR") {
|
||||
childNodes = container.childNodes;
|
||||
container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
|
||||
if (container) {
|
||||
offset = start ? 0 : container.childNodes.length;
|
||||
rng['set' + (start ? 'Start' : 'End')](container, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveEndPoint(true);
|
||||
moveEndPoint();
|
||||
|
||||
return rng;
|
||||
}
|
||||
|
||||
function getLocation() {
|
||||
var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
|
||||
|
||||
function getPoint(rng, start) {
|
||||
var container = rng[start ? 'startContainer' : 'endContainer'],
|
||||
offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
|
||||
|
||||
if (container.nodeType == 3) {
|
||||
if (normalized) {
|
||||
for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
|
||||
offset += node.nodeValue.length;
|
||||
}
|
||||
}
|
||||
|
||||
point.push(offset);
|
||||
} else {
|
||||
childNodes = container.childNodes;
|
||||
|
||||
if (offset >= childNodes.length && childNodes.length) {
|
||||
after = 1;
|
||||
offset = Math.max(0, childNodes.length - 1);
|
||||
}
|
||||
|
||||
point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
|
||||
}
|
||||
|
||||
for (; container && container != root; container = container.parentNode) {
|
||||
point.push(t.dom.nodeIndex(container, normalized));
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
bookmark.start = getPoint(rng, true);
|
||||
|
||||
if (!t.isCollapsed()) {
|
||||
bookmark.end = getPoint(rng);
|
||||
}
|
||||
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
if (type == 2) {
|
||||
element = t.getNode();
|
||||
name = element.nodeName;
|
||||
|
||||
if (name == 'IMG') {
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
|
||||
if (t.tridentSel) {
|
||||
return t.tridentSel.getBookmark(type);
|
||||
}
|
||||
|
||||
return getLocation();
|
||||
}
|
||||
|
||||
// Handle simple range
|
||||
if (type) {
|
||||
return {rng: t.getRng()};
|
||||
}
|
||||
|
||||
rng = t.getRng();
|
||||
id = dom.uniqueId();
|
||||
collapsed = t.isCollapsed();
|
||||
styles = 'overflow:hidden;line-height:0px';
|
||||
|
||||
// Explorer method
|
||||
if (rng.duplicate || rng.item) {
|
||||
// Text selection
|
||||
if (!rng.item) {
|
||||
rng2 = rng.duplicate();
|
||||
|
||||
try {
|
||||
// Insert start marker
|
||||
rng.collapse();
|
||||
rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
|
||||
|
||||
// Insert end marker
|
||||
if (!collapsed) {
|
||||
rng2.collapse(false);
|
||||
|
||||
// Detect the empty space after block elements in IE and move the
|
||||
// end back one character <p></p>] becomes <p>]</p>
|
||||
rng.moveToElementText(rng2.parentElement());
|
||||
if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
|
||||
rng2.move('character', -1);
|
||||
}
|
||||
|
||||
rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
|
||||
}
|
||||
} catch (ex) {
|
||||
// IE might throw unspecified error so lets ignore it
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Control selection
|
||||
element = rng.item(0);
|
||||
name = element.nodeName;
|
||||
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
} else {
|
||||
element = t.getNode();
|
||||
name = element.nodeName;
|
||||
if (name == 'IMG') {
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
|
||||
// W3C method
|
||||
rng2 = normalizeTableCellSelection(rng.cloneRange());
|
||||
|
||||
// Insert end marker
|
||||
if (!collapsed) {
|
||||
rng2.collapse(false);
|
||||
rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
|
||||
}
|
||||
|
||||
rng = normalizeTableCellSelection(rng);
|
||||
rng.collapse(true);
|
||||
rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
|
||||
}
|
||||
|
||||
t.moveToBookmark({id: id, keep: 1});
|
||||
|
||||
return {id: id};
|
||||
return this.bookmarkManager.getBookmark(type, normalized);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -517,150 +370,7 @@ define("tinymce/dom/Selection", [
|
||||
* tinymce.activeEditor.selection.moveToBookmark(bm);
|
||||
*/
|
||||
moveToBookmark: function(bookmark) {
|
||||
var t = this, dom = t.dom, rng, root, startContainer, endContainer, startOffset, endOffset;
|
||||
|
||||
function setEndPoint(start) {
|
||||
var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
|
||||
|
||||
if (point) {
|
||||
offset = point[0];
|
||||
|
||||
// Find container node
|
||||
for (node = root, i = point.length - 1; i >= 1; i--) {
|
||||
children = node.childNodes;
|
||||
|
||||
if (point[i] > children.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
node = children[point[i]];
|
||||
}
|
||||
|
||||
// Move text offset to best suitable location
|
||||
if (node.nodeType === 3) {
|
||||
offset = Math.min(point[0], node.nodeValue.length);
|
||||
}
|
||||
|
||||
// Move element offset to best suitable location
|
||||
if (node.nodeType === 1) {
|
||||
offset = Math.min(point[0], node.childNodes.length);
|
||||
}
|
||||
|
||||
// Set offset within container node
|
||||
if (start) {
|
||||
rng.setStart(node, offset);
|
||||
} else {
|
||||
rng.setEnd(node, offset);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function restoreEndPoint(suffix) {
|
||||
var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
|
||||
|
||||
if (marker) {
|
||||
node = marker.parentNode;
|
||||
|
||||
if (suffix == 'start') {
|
||||
if (!keep) {
|
||||
idx = dom.nodeIndex(marker);
|
||||
} else {
|
||||
node = marker.firstChild;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
startContainer = endContainer = node;
|
||||
startOffset = endOffset = idx;
|
||||
} else {
|
||||
if (!keep) {
|
||||
idx = dom.nodeIndex(marker);
|
||||
} else {
|
||||
node = marker.firstChild;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
endContainer = node;
|
||||
endOffset = idx;
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
prev = marker.previousSibling;
|
||||
next = marker.nextSibling;
|
||||
|
||||
// Remove all marker text nodes
|
||||
each(grep(marker.childNodes), function(node) {
|
||||
if (node.nodeType == 3) {
|
||||
node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
|
||||
}
|
||||
});
|
||||
|
||||
// Remove marker but keep children if for example contents where inserted into the marker
|
||||
// Also remove duplicated instances of the marker for example by a
|
||||
// split operation or by WebKit auto split on paste feature
|
||||
while ((marker = dom.get(bookmark.id + '_' + suffix))) {
|
||||
dom.remove(marker, 1);
|
||||
}
|
||||
|
||||
// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
|
||||
// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
|
||||
// isn't worth the effort. Sorry, Opera but it's just a fact
|
||||
if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) {
|
||||
idx = prev.nodeValue.length;
|
||||
prev.appendData(next.nodeValue);
|
||||
dom.remove(next);
|
||||
|
||||
if (suffix == 'start') {
|
||||
startContainer = endContainer = prev;
|
||||
startOffset = endOffset = idx;
|
||||
} else {
|
||||
endContainer = prev;
|
||||
endOffset = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addBogus(node) {
|
||||
// Adds a bogus BR element for empty block elements
|
||||
if (dom.isBlock(node) && !node.innerHTML && !isIE) {
|
||||
node.innerHTML = '<br data-mce-bogus="1" />';
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
if (bookmark) {
|
||||
if (bookmark.start) {
|
||||
rng = dom.createRng();
|
||||
root = dom.getRoot();
|
||||
|
||||
if (t.tridentSel) {
|
||||
return t.tridentSel.moveToBookmark(bookmark);
|
||||
}
|
||||
|
||||
if (setEndPoint(true) && setEndPoint()) {
|
||||
t.setRng(rng);
|
||||
}
|
||||
} else if (bookmark.id) {
|
||||
// Restore start/end points
|
||||
restoreEndPoint('start');
|
||||
restoreEndPoint('end');
|
||||
|
||||
if (startContainer) {
|
||||
rng = dom.createRng();
|
||||
rng.setStart(addBogus(startContainer), startOffset);
|
||||
rng.setEnd(addBogus(endContainer), endOffset);
|
||||
t.setRng(rng);
|
||||
}
|
||||
} else if (bookmark.name) {
|
||||
t.select(dom.select(bookmark.name)[bookmark.index]);
|
||||
} else if (bookmark.rng) {
|
||||
t.setRng(bookmark.rng);
|
||||
}
|
||||
}
|
||||
return this.bookmarkManager.moveToBookmark(bookmark);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -675,54 +385,11 @@ define("tinymce/dom/Selection", [
|
||||
* tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
|
||||
*/
|
||||
select: function(node, content) {
|
||||
var self = this, dom = self.dom, rng = dom.createRng(), idx, nonEmptyElementsMap;
|
||||
var self = this, dom = self.dom, rng = dom.createRng(), idx;
|
||||
|
||||
// Clear stored range set by FocusManager
|
||||
self.lastFocusBookmark = null;
|
||||
|
||||
nonEmptyElementsMap = dom.schema.getNonEmptyElements();
|
||||
|
||||
function setPoint(node, start) {
|
||||
var root = node, walker = new TreeWalker(node, root);
|
||||
|
||||
do {
|
||||
// Text node
|
||||
if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
|
||||
if (start) {
|
||||
rng.setStart(node, 0);
|
||||
} else {
|
||||
rng.setEnd(node, node.nodeValue.length);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// BR/IMG/INPUT elements
|
||||
if (nonEmptyElementsMap[node.nodeName]) {
|
||||
if (start) {
|
||||
rng.setStartBefore(node);
|
||||
} else {
|
||||
if (node.nodeName == 'BR') {
|
||||
rng.setEndBefore(node);
|
||||
} else {
|
||||
rng.setEndAfter(node);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} while ((node = (start ? walker.next() : walker.prev())));
|
||||
|
||||
// Failed to find any text node or other suitable location then move to the root of body
|
||||
if (root.nodeName == 'BODY') {
|
||||
if (start) {
|
||||
rng.setStart(root, 0);
|
||||
} else {
|
||||
rng.setEnd(root, root.childNodes.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node) {
|
||||
if (!content && self.controlSelection.controlSelect(node)) {
|
||||
return;
|
||||
@@ -734,8 +401,8 @@ define("tinymce/dom/Selection", [
|
||||
|
||||
// Find first/last text node or BR element
|
||||
if (content) {
|
||||
setPoint(node, 1);
|
||||
setPoint(node);
|
||||
self._moveEndPoint(rng, node, true);
|
||||
self._moveEndPoint(rng, node);
|
||||
}
|
||||
|
||||
self.setRng(rng);
|
||||
@@ -769,9 +436,9 @@ define("tinymce/dom/Selection", [
|
||||
* Collapse the selection to start or end of range.
|
||||
*
|
||||
* @method collapse
|
||||
* @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start.
|
||||
* @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to start.
|
||||
*/
|
||||
collapse: function(to_start) {
|
||||
collapse: function(toStart) {
|
||||
var self = this, rng = self.getRng(), node;
|
||||
|
||||
// Control range on IE
|
||||
@@ -781,7 +448,7 @@ define("tinymce/dom/Selection", [
|
||||
rng.moveToElementText(node);
|
||||
}
|
||||
|
||||
rng.collapse(!!to_start);
|
||||
rng.collapse(!!toStart);
|
||||
self.setRng(rng);
|
||||
},
|
||||
|
||||
@@ -809,9 +476,18 @@ define("tinymce/dom/Selection", [
|
||||
getRng: function(w3c) {
|
||||
var self = this, selection, rng, elm, doc = self.win.document, ieRng;
|
||||
|
||||
// if(aa) {
|
||||
// log("..");
|
||||
// }
|
||||
function tryCompareBoundaryPoints(how, sourceRange, destinationRange) {
|
||||
try {
|
||||
return sourceRange.compareBoundaryPoints(how, destinationRange);
|
||||
} catch (ex) {
|
||||
// Gecko throws wrong document exception if the range points
|
||||
// to nodes that where removed from the dom #6690
|
||||
// Browsers should mutate existing DOMRange instances so that they always point
|
||||
// to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
|
||||
// For performance reasons just return -1
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Use last rng passed from FocusManager if it's available this enables
|
||||
// calls to editor.selection.getStart() to work when caret focus is lost on IE
|
||||
@@ -819,12 +495,17 @@ define("tinymce/dom/Selection", [
|
||||
var bookmark = self.lastFocusBookmark;
|
||||
|
||||
// Convert bookmark to range IE 11 fix
|
||||
if (bookmark.startContainer) {
|
||||
rng = doc.createRange();
|
||||
rng.setStart(bookmark.startContainer, bookmark.startOffset);
|
||||
rng.setEnd(bookmark.endContainer, bookmark.endOffset);
|
||||
} else {
|
||||
rng = bookmark;
|
||||
// life ace, 不知道会出什么错
|
||||
try {
|
||||
if (bookmark.startContainer) {
|
||||
rng = doc.createRange();
|
||||
rng.setStart(bookmark.startContainer, bookmark.startOffset);
|
||||
rng.setEnd(bookmark.endContainer, bookmark.endOffset);
|
||||
} else {
|
||||
rng = bookmark;
|
||||
}
|
||||
} catch(e) {
|
||||
console.trace(e);
|
||||
}
|
||||
|
||||
return rng;
|
||||
@@ -848,7 +529,8 @@ define("tinymce/dom/Selection", [
|
||||
}
|
||||
|
||||
// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
|
||||
if (isIE && rng && rng.setStart) {
|
||||
// IE 11 doesn't support the selection object so we check for that as well
|
||||
if (isIE && rng && rng.setStart && doc.selection) {
|
||||
try {
|
||||
// IE will sometimes throw an exception here
|
||||
ieRng = doc.selection.createRange();
|
||||
@@ -879,8 +561,8 @@ define("tinymce/dom/Selection", [
|
||||
}
|
||||
|
||||
if (self.selectedRange && self.explicitRange) {
|
||||
if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 &&
|
||||
rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
|
||||
if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
|
||||
tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
|
||||
// Safari, Opera and Chrome only ever select text which causes the range to change.
|
||||
// This lets us use the originally set range if the selection hasn't been changed by the user.
|
||||
rng = self.explicitRange;
|
||||
@@ -890,9 +572,6 @@ define("tinymce/dom/Selection", [
|
||||
}
|
||||
}
|
||||
|
||||
// log(">>")
|
||||
// log((new Date()).getTime())
|
||||
// log(rng);
|
||||
return rng;
|
||||
},
|
||||
|
||||
@@ -905,6 +584,10 @@ define("tinymce/dom/Selection", [
|
||||
setRng: function(rng, forward) {
|
||||
var self = this, sel;
|
||||
|
||||
if (!rng) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is IE specific range
|
||||
if (rng.select) {
|
||||
try {
|
||||
@@ -970,7 +653,6 @@ define("tinymce/dom/Selection", [
|
||||
},
|
||||
|
||||
/**
|
||||
* 这里, 很重要
|
||||
* Returns the currently selected element or the common ancestor element for both start and end of the selection.
|
||||
*
|
||||
* @method getNode
|
||||
@@ -981,13 +663,8 @@ define("tinymce/dom/Selection", [
|
||||
*/
|
||||
getNode: function() {
|
||||
var self = this, rng = self.getRng(), elm;
|
||||
// log(rng);
|
||||
var startContainer = rng.startContainer, endContainer = rng.endContainer;
|
||||
var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot();
|
||||
// log('start');
|
||||
// log(startContainer);
|
||||
// log('end');
|
||||
// log(endContainer);
|
||||
|
||||
function skipEmptyTextNodes(node, forwards) {
|
||||
var orig = node;
|
||||
@@ -1088,7 +765,7 @@ define("tinymce/dom/Selection", [
|
||||
return selectedBlocks;
|
||||
},
|
||||
|
||||
isForward: function(){
|
||||
isForward: function() {
|
||||
var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
|
||||
|
||||
// No support for selection direction then always return true
|
||||
@@ -1108,182 +785,13 @@ define("tinymce/dom/Selection", [
|
||||
},
|
||||
|
||||
normalize: function() {
|
||||
var self = this, rng, normalized, collapsed;
|
||||
var self = this, rng = self.getRng();
|
||||
|
||||
function normalizeEndPoint(start) {
|
||||
var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
|
||||
|
||||
function hasBrBeforeAfter(node, left) {
|
||||
var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
|
||||
|
||||
while ((node = walker[left ? 'prev' : 'next']())) {
|
||||
if (node.nodeName === "BR") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPrevNode(node, name) {
|
||||
return node.previousSibling && node.previousSibling.nodeName == name;
|
||||
}
|
||||
|
||||
// Walks the dom left/right to find a suitable text node to move the endpoint into
|
||||
// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
|
||||
function findTextNodeRelative(left, startNode) {
|
||||
var walker, lastInlineElement;
|
||||
|
||||
startNode = startNode || container;
|
||||
walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
|
||||
|
||||
// Walk left until we hit a text node we can move to or a block/br/img
|
||||
while ((node = walker[left ? 'prev' : 'next']())) {
|
||||
// Found text node that has a length
|
||||
if (node.nodeType === 3 && node.nodeValue.length > 0) {
|
||||
container = node;
|
||||
offset = left ? node.nodeValue.length : 0;
|
||||
normalized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Break if we find a block or a BR/IMG/INPUT etc
|
||||
if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastInlineElement = node;
|
||||
}
|
||||
|
||||
// Only fetch the last inline element when in caret mode for now
|
||||
if (collapsed && lastInlineElement) {
|
||||
container = lastInlineElement;
|
||||
normalized = true;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
container = rng[(start ? 'start' : 'end') + 'Container'];
|
||||
offset = rng[(start ? 'start' : 'end') + 'Offset'];
|
||||
nonEmptyElementsMap = dom.schema.getNonEmptyElements();
|
||||
|
||||
// If the container is a document move it to the body element
|
||||
if (container.nodeType === 9) {
|
||||
container = dom.getRoot();
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
// If the container is body try move it into the closest text node or position
|
||||
if (container === body) {
|
||||
// If start is before/after a image, table etc
|
||||
if (start) {
|
||||
node = container.childNodes[offset > 0 ? offset - 1 : 0];
|
||||
if (node) {
|
||||
nodeName = node.nodeName.toLowerCase();
|
||||
if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the index
|
||||
if (container.hasChildNodes()) {
|
||||
offset = Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
|
||||
container = container.childNodes[offset];
|
||||
offset = 0;
|
||||
|
||||
// Don't walk into elements that doesn't have any child nodes like a IMG
|
||||
if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
|
||||
// Walk the DOM to find a text node to place the caret at or a BR
|
||||
node = container;
|
||||
walker = new TreeWalker(container, body);
|
||||
|
||||
do {
|
||||
// Found a text node use that position
|
||||
if (node.nodeType === 3 && node.nodeValue.length > 0) {
|
||||
offset = start ? 0 : node.nodeValue.length;
|
||||
container = node;
|
||||
normalized = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Found a BR/IMG element that we can place the caret before
|
||||
if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
||||
offset = dom.nodeIndex(node);
|
||||
container = node.parentNode;
|
||||
|
||||
// Put caret after image when moving the end point
|
||||
if (node.nodeName == "IMG" && !start) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
normalized = true;
|
||||
break;
|
||||
}
|
||||
} while ((node = (start ? walker.next() : walker.prev())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lean the caret to the left if possible
|
||||
if (collapsed) {
|
||||
// So this: <b>x</b><i>|x</i>
|
||||
// Becomes: <b>x|</b><i>x</i>
|
||||
// Seems that only gecko has issues with this
|
||||
if (container.nodeType === 3 && offset === 0) {
|
||||
findTextNodeRelative(true);
|
||||
}
|
||||
|
||||
// Lean left into empty inline elements when the caret is before a BR
|
||||
// So this: <i><b></b><i>|<br></i>
|
||||
// Becomes: <i><b>|</b><i><br></i>
|
||||
// Seems that only gecko has issues with this.
|
||||
// Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
|
||||
if (container.nodeType === 1) {
|
||||
node = container.childNodes[offset];
|
||||
if(node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
|
||||
!hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
|
||||
findTextNodeRelative(true, container.childNodes[offset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lean the start of the selection right if possible
|
||||
// So this: x[<b>x]</b>
|
||||
// Becomes: x<b>[x]</b>
|
||||
if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
|
||||
findTextNodeRelative(false);
|
||||
}
|
||||
|
||||
// Set endpoint if it was normalized
|
||||
if (normalized) {
|
||||
rng['set' + (start ? 'Start' : 'End')](container, offset);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize only on non IE browsers for now
|
||||
if (isIE) {
|
||||
return;
|
||||
}
|
||||
|
||||
rng = self.getRng();
|
||||
collapsed = rng.collapsed;
|
||||
|
||||
// Normalize the end points
|
||||
normalizeEndPoint(true);
|
||||
|
||||
if (!collapsed) {
|
||||
normalizeEndPoint();
|
||||
}
|
||||
|
||||
// Set the selection if it was normalized
|
||||
if (normalized) {
|
||||
// If it was collapsed then make sure it still is
|
||||
if (collapsed) {
|
||||
rng.collapse(true);
|
||||
}
|
||||
|
||||
//console.log(self.dom.dumpRng(rng));
|
||||
if (Env.range && new RangeUtils(self.dom).normalize(rng)) {
|
||||
self.setRng(rng, self.isForward());
|
||||
}
|
||||
|
||||
return rng;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1399,6 +907,83 @@ define("tinymce/dom/Selection", [
|
||||
}
|
||||
},
|
||||
|
||||
placeCaretAt: function(clientX, clientY) {
|
||||
var doc = this.editor.getDoc(), rng, point;
|
||||
|
||||
if (doc.caretPositionFromPoint) {
|
||||
point = doc.caretPositionFromPoint(clientX, clientY);
|
||||
rng = doc.createRange();
|
||||
rng.setStart(point.offsetNode, point.offset);
|
||||
rng.collapse(true);
|
||||
} else if (doc.caretRangeFromPoint) {
|
||||
rng = doc.caretRangeFromPoint(clientX, clientY);
|
||||
} else if (doc.body.createTextRange) {
|
||||
rng = doc.body.createTextRange();
|
||||
|
||||
try {
|
||||
rng.moveToPoint(clientX, clientY);
|
||||
rng.collapse(true);
|
||||
} catch (ex) {
|
||||
rng.collapse(clientY < doc.body.clientHeight);
|
||||
}
|
||||
}
|
||||
|
||||
this.setRng(rng);
|
||||
},
|
||||
|
||||
_moveEndPoint: function(rng, node, start) {
|
||||
var root = node, walker = new TreeWalker(node, root);
|
||||
var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
|
||||
|
||||
do {
|
||||
// Text node
|
||||
if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
|
||||
if (start) {
|
||||
rng.setStart(node, 0);
|
||||
} else {
|
||||
rng.setEnd(node, node.nodeValue.length);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// BR/IMG/INPUT elements but not table cells
|
||||
if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) {
|
||||
if (start) {
|
||||
rng.setStartBefore(node);
|
||||
} else {
|
||||
if (node.nodeName == 'BR') {
|
||||
rng.setEndBefore(node);
|
||||
} else {
|
||||
rng.setEndAfter(node);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Found empty text block old IE can place the selection inside those
|
||||
if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
|
||||
if (start) {
|
||||
rng.setStart(node, 0);
|
||||
} else {
|
||||
rng.setEnd(node, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} while ((node = (start ? walker.next() : walker.prev())));
|
||||
|
||||
// Failed to find any text node or other suitable location then move to the root of body
|
||||
if (root.nodeName == 'BODY') {
|
||||
if (start) {
|
||||
rng.setStart(root, 0);
|
||||
} else {
|
||||
rng.setEnd(root, root.childNodes.length);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.win = null;
|
||||
this.controlSelection.destroy();
|
||||
|
||||
Regular → Executable
+36
-20
@@ -51,6 +51,17 @@ define("tinymce/dom/Serializer", [
|
||||
|
||||
htmlParser = new DomParser(settings, schema);
|
||||
|
||||
// Convert tabindex back to elements when serializing contents
|
||||
htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
|
||||
var i = nodes.length, node;
|
||||
|
||||
while (i--) {
|
||||
node = nodes[i];
|
||||
node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
|
||||
node.attr(name, null);
|
||||
}
|
||||
});
|
||||
|
||||
// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
|
||||
htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
|
||||
var i = nodes.length, node, value, internalName = 'data-mce-' + name;
|
||||
@@ -85,8 +96,12 @@ define("tinymce/dom/Serializer", [
|
||||
|
||||
while (i--) {
|
||||
node = nodes[i];
|
||||
value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
|
||||
node.attr('class', value.length > 0 ? value : null);
|
||||
value = node.attr('class');
|
||||
|
||||
if (value) {
|
||||
value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
|
||||
node.attr('class', value.length > 0 ? value : null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -103,15 +118,6 @@ define("tinymce/dom/Serializer", [
|
||||
}
|
||||
});
|
||||
|
||||
// Remove expando attributes
|
||||
htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) {
|
||||
var i = nodes.length;
|
||||
|
||||
while (i--) {
|
||||
nodes[i].attr(name, null);
|
||||
}
|
||||
});
|
||||
|
||||
htmlParser.addNodeFilter('noscript', function(nodes) {
|
||||
var i = nodes.length, node;
|
||||
|
||||
@@ -126,10 +132,11 @@ define("tinymce/dom/Serializer", [
|
||||
|
||||
// Force script into CDATA sections and remove the mce- prefix also add comments around styles
|
||||
htmlParser.addNodeFilter('script,style', function(nodes, name) {
|
||||
var i = nodes.length, node, value;
|
||||
var i = nodes.length, node, value, type;
|
||||
|
||||
function trim(value) {
|
||||
/*jshint maxlen:255 */
|
||||
/*eslint max-len:0 */
|
||||
return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
|
||||
.replace(/^[\r\n]*|[\r\n]*$/g, '')
|
||||
.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
|
||||
@@ -141,9 +148,12 @@ define("tinymce/dom/Serializer", [
|
||||
value = node.firstChild ? node.firstChild.value : '';
|
||||
|
||||
if (name === "script") {
|
||||
// Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5)
|
||||
var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, '');
|
||||
node.attr('type', type === 'text/javascript' ? null : type);
|
||||
// Remove mce- prefix from script elements and remove default type since the user specified
|
||||
// a script element without type attribute
|
||||
type = node.attr('type');
|
||||
if (type) {
|
||||
node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
|
||||
}
|
||||
|
||||
if (value.length > 0) {
|
||||
node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
|
||||
@@ -210,13 +220,19 @@ define("tinymce/dom/Serializer", [
|
||||
}
|
||||
|
||||
// Remove internal data attributes
|
||||
htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) {
|
||||
var i = nodes.length;
|
||||
htmlParser.addAttributeFilter(
|
||||
'data-mce-src,data-mce-href,data-mce-style,' +
|
||||
'data-mce-selected,data-mce-expando,' +
|
||||
'data-mce-type,data-mce-resize',
|
||||
|
||||
while (i--) {
|
||||
nodes[i].attr(name, null);
|
||||
function(nodes, name) {
|
||||
var i = nodes.length;
|
||||
|
||||
while (i--) {
|
||||
nodes[i].attr(name, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Return public methods
|
||||
return {
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+569
-458
File diff suppressed because it is too large.
Load diff
+189
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* StyleSheetLoader.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class handles loading of external stylesheets and fires events when these are loaded.
|
||||
*
|
||||
* @class tinymce.dom.StyleSheetLoader
|
||||
* @private
|
||||
*/
|
||||
define("tinymce/dom/StyleSheetLoader", [
|
||||
"tinymce/util/Tools"
|
||||
], function(Tools) {
|
||||
"use strict";
|
||||
|
||||
return function(document, settings) {
|
||||
var idCount = 0, loadedStates = {}, maxLoadTime;
|
||||
|
||||
settings = settings || {};
|
||||
maxLoadTime = settings.maxLoadTime || 5000;
|
||||
|
||||
function appendToHead(node) {
|
||||
document.getElementsByTagName('head')[0].appendChild(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
|
||||
*
|
||||
* @method load
|
||||
* @param {String} url Url to be loaded.
|
||||
* @param {Function} loadedCallback Callback to be executed when loaded.
|
||||
* @param {Function} errorCallback Callback to be executed when failed loading.
|
||||
*/
|
||||
function load(url, loadedCallback, errorCallback) {
|
||||
var link, style, startTime, state;
|
||||
|
||||
function passed() {
|
||||
var callbacks = state.passed, i = callbacks.length;
|
||||
|
||||
while (i--) {
|
||||
callbacks[i]();
|
||||
}
|
||||
|
||||
state.status = 2;
|
||||
state.passed = [];
|
||||
state.failed = [];
|
||||
}
|
||||
|
||||
function failed() {
|
||||
var callbacks = state.failed, i = callbacks.length;
|
||||
|
||||
while (i--) {
|
||||
callbacks[i]();
|
||||
}
|
||||
|
||||
state.status = 3;
|
||||
state.passed = [];
|
||||
state.failed = [];
|
||||
}
|
||||
|
||||
// Sniffs for older WebKit versions that have the link.onload but a broken one
|
||||
function isOldWebKit() {
|
||||
var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
|
||||
return !!(webKitChunks && webKitChunks[1] < 536);
|
||||
}
|
||||
|
||||
// Calls the waitCallback until the test returns true or the timeout occurs
|
||||
function wait(testCallback, waitCallback) {
|
||||
if (!testCallback()) {
|
||||
// Wait for timeout
|
||||
if ((new Date().getTime()) - startTime < maxLoadTime) {
|
||||
window.setTimeout(waitCallback, 0);
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for WebKit that doesn't properly support the onload event for link elements
|
||||
// Or WebKit that fires the onload event before the StyleSheet is added to the document
|
||||
function waitForWebKitLinkLoaded() {
|
||||
wait(function() {
|
||||
var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
|
||||
|
||||
while (i--) {
|
||||
styleSheet = styleSheets[i];
|
||||
owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
|
||||
if (owner && owner.id === link.id) {
|
||||
passed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}, waitForWebKitLinkLoaded);
|
||||
}
|
||||
|
||||
// Workaround for older Geckos that doesn't have any onload event for StyleSheets
|
||||
function waitForGeckoLinkLoaded() {
|
||||
wait(function() {
|
||||
try {
|
||||
// Accessing the cssRules will throw an exception until the CSS file is loaded
|
||||
var cssRules = style.sheet.cssRules;
|
||||
passed();
|
||||
return !!cssRules;
|
||||
} catch (ex) {
|
||||
// Ignore
|
||||
}
|
||||
}, waitForGeckoLinkLoaded);
|
||||
}
|
||||
|
||||
url = Tools._addCacheSuffix(url);
|
||||
|
||||
if (!loadedStates[url]) {
|
||||
state = {
|
||||
passed: [],
|
||||
failed: []
|
||||
};
|
||||
|
||||
loadedStates[url] = state;
|
||||
} else {
|
||||
state = loadedStates[url];
|
||||
}
|
||||
|
||||
if (loadedCallback) {
|
||||
state.passed.push(loadedCallback);
|
||||
}
|
||||
|
||||
if (errorCallback) {
|
||||
state.failed.push(errorCallback);
|
||||
}
|
||||
|
||||
// Is loading wait for it to pass
|
||||
if (state.status == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Has finished loading and was success
|
||||
if (state.status == 2) {
|
||||
passed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Has finished loading and was a failure
|
||||
if (state.status == 3) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start loading
|
||||
state.status = 1;
|
||||
link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.id = 'u' + (idCount++);
|
||||
link.async = false;
|
||||
link.defer = false;
|
||||
startTime = new Date().getTime();
|
||||
|
||||
// Feature detect onload on link element and sniff older webkits since it has an broken onload event
|
||||
if ("onload" in link && !isOldWebKit()) {
|
||||
link.onload = waitForWebKitLinkLoaded;
|
||||
link.onerror = failed;
|
||||
} else {
|
||||
// Sniff for old Firefox that doesn't support the onload event on link elements
|
||||
// TODO: Remove this in the future when everyone uses modern browsers
|
||||
if (navigator.userAgent.indexOf("Firefox") > 0) {
|
||||
style = document.createElement('style');
|
||||
style.textContent = '@import "' + url + '"';
|
||||
waitForGeckoLinkLoaded();
|
||||
appendToHead(style);
|
||||
return;
|
||||
} else {
|
||||
// Use the id owner on older webkits
|
||||
waitForWebKitLinkLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
appendToHead(link);
|
||||
link.href = url;
|
||||
}
|
||||
|
||||
this.load = load;
|
||||
};
|
||||
});
|
||||
Regular → Executable
+23
-9
@@ -12,30 +12,44 @@
|
||||
* TreeWalker class enables you to walk the DOM in a linear manner.
|
||||
*
|
||||
* @class tinymce.dom.TreeWalker
|
||||
* @example
|
||||
* var walker = new tinymce.dom.TreeWalker(startNode);
|
||||
*
|
||||
* do {
|
||||
* console.log(walker.current());
|
||||
* } while (walker.next());
|
||||
*/
|
||||
define("tinymce/dom/TreeWalker", [], function() {
|
||||
return function(start_node, root_node) {
|
||||
var node = start_node;
|
||||
/**
|
||||
* Constructs a new TreeWalker instance.
|
||||
*
|
||||
* @constructor
|
||||
* @method TreeWalker
|
||||
* @param {Node} startNode Node to start walking from.
|
||||
* @param {node} rootNode Optional root node to never walk out of.
|
||||
*/
|
||||
return function(startNode, rootNode) {
|
||||
var node = startNode;
|
||||
|
||||
function findSibling(node, start_name, sibling_name, shallow) {
|
||||
function findSibling(node, startName, siblingName, shallow) {
|
||||
var sibling, parent;
|
||||
|
||||
if (node) {
|
||||
// Walk into nodes if it has a start
|
||||
if (!shallow && node[start_name]) {
|
||||
return node[start_name];
|
||||
if (!shallow && node[startName]) {
|
||||
return node[startName];
|
||||
}
|
||||
|
||||
// Return the sibling if it has one
|
||||
if (node != root_node) {
|
||||
sibling = node[sibling_name];
|
||||
if (node != rootNode) {
|
||||
sibling = node[siblingName];
|
||||
if (sibling) {
|
||||
return sibling;
|
||||
}
|
||||
|
||||
// Walk up the parents to look for siblings
|
||||
for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
|
||||
sibling = parent[sibling_name];
|
||||
for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
|
||||
sibling = parent[siblingName];
|
||||
if (sibling) {
|
||||
return sibling;
|
||||
}
|
||||
|
||||
Regular → Executable
+18
-14
@@ -163,15 +163,17 @@ define("tinymce/dom/TridentSelection", [], function() {
|
||||
|
||||
// Find the text node and offset
|
||||
while (sibling) {
|
||||
nodeValue = sibling.nodeValue;
|
||||
textNodeOffset += nodeValue.length;
|
||||
if (sibling.nodeType == 3) {
|
||||
nodeValue = sibling.nodeValue;
|
||||
textNodeOffset += nodeValue.length;
|
||||
|
||||
// We are at or passed the position we where looking for
|
||||
if (textNodeOffset >= offset) {
|
||||
container = sibling;
|
||||
textNodeOffset -= offset;
|
||||
textNodeOffset = nodeValue.length - textNodeOffset;
|
||||
break;
|
||||
// We are at or passed the position we where looking for
|
||||
if (textNodeOffset >= offset) {
|
||||
container = sibling;
|
||||
textNodeOffset -= offset;
|
||||
textNodeOffset = nodeValue.length - textNodeOffset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sibling = sibling.nextSibling;
|
||||
@@ -196,13 +198,15 @@ define("tinymce/dom/TridentSelection", [], function() {
|
||||
}
|
||||
|
||||
while (sibling) {
|
||||
textNodeOffset += sibling.nodeValue.length;
|
||||
if (sibling.nodeType == 3) {
|
||||
textNodeOffset += sibling.nodeValue.length;
|
||||
|
||||
// We are at or passed the position we where looking for
|
||||
if (textNodeOffset >= offset) {
|
||||
container = sibling;
|
||||
textNodeOffset -= offset;
|
||||
break;
|
||||
// We are at or passed the position we where looking for
|
||||
if (textNodeOffset >= offset) {
|
||||
container = sibling;
|
||||
textNodeOffset -= offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sibling = sibling.previousSibling;
|
||||
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Preview.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal class for generating previews styles for formats.
|
||||
*
|
||||
* Example:
|
||||
* Preview.getCssText(editor, 'bold');
|
||||
*
|
||||
* @class tinymce.fmt.Preview
|
||||
* @private
|
||||
*/
|
||||
define("tinymce/fmt/Preview", [
|
||||
"tinymce/util/Tools"
|
||||
], function(Tools) {
|
||||
var each = Tools.each;
|
||||
|
||||
function getCssText(editor, format) {
|
||||
var name, previewElm, dom = editor.dom;
|
||||
var previewCss = '', parentFontSize, previewStyles;
|
||||
|
||||
previewStyles = editor.settings.preview_styles;
|
||||
|
||||
// No preview forced
|
||||
if (previewStyles === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Default preview
|
||||
if (!previewStyles) {
|
||||
previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
|
||||
'text-transform color background-color border border-radius outline text-shadow';
|
||||
}
|
||||
|
||||
// Removes any variables since these can't be previewed
|
||||
function removeVars(val) {
|
||||
return val.replace(/%(\w+)/g, '');
|
||||
}
|
||||
|
||||
// Create block/inline element to use for preview
|
||||
if (typeof format == "string") {
|
||||
format = editor.formatter.get(format);
|
||||
if (!format) {
|
||||
return;
|
||||
}
|
||||
|
||||
format = format[0];
|
||||
}
|
||||
|
||||
name = format.block || format.inline || 'span';
|
||||
previewElm = dom.create(name);
|
||||
|
||||
// Add format styles to preview element
|
||||
each(format.styles, function(value, name) {
|
||||
value = removeVars(value);
|
||||
|
||||
if (value) {
|
||||
dom.setStyle(previewElm, name, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Add attributes to preview element
|
||||
each(format.attributes, function(value, name) {
|
||||
value = removeVars(value);
|
||||
|
||||
if (value) {
|
||||
dom.setAttrib(previewElm, name, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Add classes to preview element
|
||||
each(format.classes, function(value) {
|
||||
value = removeVars(value);
|
||||
|
||||
if (!dom.hasClass(previewElm, value)) {
|
||||
dom.addClass(previewElm, value);
|
||||
}
|
||||
});
|
||||
|
||||
editor.fire('PreviewFormats');
|
||||
|
||||
// Add the previewElm outside the visual area
|
||||
dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
|
||||
editor.getBody().appendChild(previewElm);
|
||||
|
||||
// Get parent container font size so we can compute px values out of em/% for older IE:s
|
||||
parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
|
||||
parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
|
||||
|
||||
each(previewStyles.split(' '), function(name) {
|
||||
var value = dom.getStyle(previewElm, name, true);
|
||||
|
||||
// If background is transparent then check if the body has a background color we can use
|
||||
if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
|
||||
value = dom.getStyle(editor.getBody(), name, true);
|
||||
|
||||
// Ignore white since it's the default color, not the nicest fix
|
||||
// TODO: Fix this by detecting runtime style
|
||||
if (dom.toHex(value).toLowerCase() == '#ffffff') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (name == 'color') {
|
||||
// Ignore black since it's the default color, not the nicest fix
|
||||
// TODO: Fix this by detecting runtime style
|
||||
if (dom.toHex(value).toLowerCase() == '#000000') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Old IE won't calculate the font size so we need to do that manually
|
||||
if (name == 'font-size') {
|
||||
if (/em|%$/.test(value)) {
|
||||
if (parentFontSize === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert font size from em/% to px
|
||||
value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
|
||||
value = (value * parentFontSize) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
if (name == "border" && value) {
|
||||
previewCss += 'padding:0 2px;';
|
||||
}
|
||||
|
||||
previewCss += name + ':' + value + ';';
|
||||
});
|
||||
|
||||
editor.fire('AfterPreviewFormats');
|
||||
|
||||
//previewCss += 'line-height:normal';
|
||||
|
||||
dom.remove(previewElm);
|
||||
|
||||
return previewCss;
|
||||
}
|
||||
|
||||
return {
|
||||
getCssText: getCssText
|
||||
};
|
||||
});
|
||||
Regular → Executable
+51
-3
@@ -106,7 +106,7 @@ define("tinymce/html/DomParser", [
|
||||
tempNode = currentNode;
|
||||
}
|
||||
|
||||
for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
|
||||
for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) {
|
||||
nextNode = childNode.next;
|
||||
tempNode.append(childNode);
|
||||
childNode = nextNode;
|
||||
@@ -355,7 +355,7 @@ define("tinymce/html/DomParser", [
|
||||
function removeWhitespaceBefore(node) {
|
||||
var textNode, textVal, sibling;
|
||||
|
||||
for (textNode = node.prev; textNode && textNode.type === 3; ) {
|
||||
for (textNode = node.prev; textNode && textNode.type === 3;) {
|
||||
textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
|
||||
|
||||
if (textVal.length > 0) {
|
||||
@@ -573,7 +573,13 @@ define("tinymce/html/DomParser", [
|
||||
// Leave nodes that have a name like <a name="name">
|
||||
if (!node.attributes.map.name && !node.attributes.map.id) {
|
||||
tempNode = node.parent;
|
||||
node.empty().remove();
|
||||
|
||||
if (blockElements[node.name]) {
|
||||
node.empty().remove();
|
||||
} else {
|
||||
node.unwrap();
|
||||
}
|
||||
|
||||
node = tempNode;
|
||||
return;
|
||||
}
|
||||
@@ -752,5 +758,47 @@ define("tinymce/html/DomParser", [
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.validate && schema.getValidClasses()) {
|
||||
self.addAttributeFilter('class', function(nodes) {
|
||||
var i = nodes.length, node, classList, ci, className, classValue;
|
||||
var validClasses = schema.getValidClasses(), validClassesMap, valid;
|
||||
|
||||
while (i--) {
|
||||
node = nodes[i];
|
||||
classList = node.attr('class').split(' ');
|
||||
classValue = '';
|
||||
|
||||
for (ci = 0; ci < classList.length; ci++) {
|
||||
className = classList[ci];
|
||||
valid = false;
|
||||
|
||||
validClassesMap = validClasses['*'];
|
||||
if (validClassesMap && validClassesMap[className]) {
|
||||
valid = true;
|
||||
}
|
||||
|
||||
validClassesMap = validClasses[node.name];
|
||||
if (!valid && validClassesMap && validClassesMap[className]) {
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
if (classValue) {
|
||||
classValue += ' ';
|
||||
}
|
||||
|
||||
classValue += className;
|
||||
}
|
||||
}
|
||||
|
||||
if (!classValue.length) {
|
||||
classValue = null;
|
||||
}
|
||||
|
||||
node.attr('class', classValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
Regular → Executable
+4
-2
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
/*jshint bitwise:false */
|
||||
/*eslint no-bitwise:0 */
|
||||
|
||||
/**
|
||||
* Entity encoder class.
|
||||
@@ -23,7 +24,7 @@ define("tinymce/html/Entities", [
|
||||
var makeMap = Tools.makeMap;
|
||||
|
||||
var namedEntities, baseEntities, reverseEntities,
|
||||
attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
||||
attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
||||
textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
||||
rawCharsRegExp = /[<>&\"\']/g,
|
||||
entityRegExp = /&(#x|#)?([\w]+);/g,
|
||||
@@ -41,7 +42,8 @@ define("tinymce/html/Entities", [
|
||||
"'": ''',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&'
|
||||
'&': '&',
|
||||
'\u0060': '`'
|
||||
};
|
||||
|
||||
// Reverse lookup table for raw entities
|
||||
|
||||
Regular → Executable
+3
-3
@@ -229,7 +229,7 @@ define("tinymce/html/Node", [], function() {
|
||||
unwrap: function() {
|
||||
var self = this, node, next;
|
||||
|
||||
for (node = self.firstChild; node; ) {
|
||||
for (node = self.firstChild; node;) {
|
||||
next = node.next;
|
||||
self.insert(node, self, true);
|
||||
node = next;
|
||||
@@ -431,11 +431,11 @@ define("tinymce/html/Node", [], function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep elements with data attributes or name attribute like <a name="1"></a>
|
||||
// Keep bookmark nodes and name attribute like <a name="1"></a>
|
||||
i = node.attributes.length;
|
||||
while (i--) {
|
||||
name = node.attributes[i].name;
|
||||
if (name === "name" || name.indexOf('data-mce-') === 0) {
|
||||
if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+78
-9
@@ -8,6 +8,8 @@
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/*eslint max-depth:[2, 9] */
|
||||
|
||||
/**
|
||||
* This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
|
||||
* always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
|
||||
@@ -55,6 +57,45 @@ define("tinymce/html/SaxParser", [
|
||||
], function(Schema, Entities, Tools) {
|
||||
var each = Tools.each;
|
||||
|
||||
/**
|
||||
* Returns the index of the end tag for a specific start tag. This can be
|
||||
* used to skip all children of a parent element from being processed.
|
||||
*
|
||||
* @private
|
||||
* @method findEndTag
|
||||
* @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements.
|
||||
* @param {String} html HTML string to find the end tag in.
|
||||
* @param {Number} startIndex Indext to start searching at should be after the start tag.
|
||||
* @return {Number} Index of the end tag.
|
||||
*/
|
||||
function findEndTag(schema, html, startIndex) {
|
||||
var count = 1, index, matches, tokenRegExp, shortEndedElements;
|
||||
|
||||
shortEndedElements = schema.getShortEndedElements();
|
||||
tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
|
||||
tokenRegExp.lastIndex = index = startIndex;
|
||||
|
||||
while ((matches = tokenRegExp.exec(html))) {
|
||||
index = tokenRegExp.lastIndex;
|
||||
|
||||
if (matches[1] === '/') { // End element
|
||||
count--;
|
||||
} else if (!matches[1]) { // Start element
|
||||
if (matches[2] in shortEndedElements) {
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new SaxParser instance.
|
||||
*
|
||||
@@ -63,8 +104,10 @@ define("tinymce/html/SaxParser", [
|
||||
* @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
|
||||
* @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
|
||||
*/
|
||||
return function(settings, schema) {
|
||||
var self = this, noop = function() {};
|
||||
function SaxParser(settings, schema) {
|
||||
var self = this;
|
||||
|
||||
function noop() {}
|
||||
|
||||
settings = settings || {};
|
||||
self.schema = schema = schema || new Schema();
|
||||
@@ -94,7 +137,8 @@ define("tinymce/html/SaxParser", [
|
||||
var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
|
||||
var attributesRequired, attributesDefault, attributesForced;
|
||||
var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
|
||||
var decode = Entities.decode, fixSelfClosing, filteredAttrs = Tools.makeMap('src,href');
|
||||
var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
|
||||
var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
|
||||
|
||||
function processEndTag(name) {
|
||||
var pos, i;
|
||||
@@ -160,8 +204,23 @@ define("tinymce/html/SaxParser", [
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredAttrs[name] && !settings.allow_script_urls) {
|
||||
if (/(java|vb)script:/i.test(decodeURIComponent(value.replace(trimRegExp, '')))) {
|
||||
// Block any javascript: urls or non image data uris
|
||||
if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
|
||||
var uri = value.replace(trimRegExp, '');
|
||||
|
||||
try {
|
||||
// Might throw malformed URI sequence
|
||||
uri = decodeURIComponent(uri);
|
||||
} catch (ex) {
|
||||
// Fallback to non UTF-8 decoder
|
||||
uri = unescape(uri);
|
||||
}
|
||||
|
||||
if (scriptUriRegExp.test(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -181,7 +240,7 @@ define("tinymce/html/SaxParser", [
|
||||
'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
|
||||
'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
|
||||
'(?:\\/([^>]+)>)|' + // End element
|
||||
'(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
|
||||
'(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
|
||||
')', 'g');
|
||||
|
||||
attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
|
||||
@@ -318,7 +377,13 @@ define("tinymce/html/SaxParser", [
|
||||
}
|
||||
|
||||
// Invalidate element if it's marked as bogus
|
||||
if (attrList.map['data-mce-bogus']) {
|
||||
if ((attr = attrList.map['data-mce-bogus'])) {
|
||||
if (attr === 'all') {
|
||||
index = findEndTag(schema, html, tokenRegExp.lastIndex);
|
||||
tokenRegExp.lastIndex = index;
|
||||
continue;
|
||||
}
|
||||
|
||||
isValidElement = false;
|
||||
}
|
||||
}
|
||||
@@ -401,5 +466,9 @@ define("tinymce/html/SaxParser", [
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
SaxParser.findEndTag = findEndTag;
|
||||
|
||||
return SaxParser;
|
||||
});
|
||||
Regular → Executable
+121
-48
@@ -25,7 +25,7 @@
|
||||
define("tinymce/html/Schema", [
|
||||
"tinymce/util/Tools"
|
||||
], function(Tools) {
|
||||
var mapCache = {};
|
||||
var mapCache = {}, dummyObj = {};
|
||||
var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
|
||||
|
||||
function split(items, delim) {
|
||||
@@ -40,17 +40,17 @@ define("tinymce/html/Schema", [
|
||||
* @return {Object} Schema lookup table.
|
||||
*/
|
||||
function compileSchema(type) {
|
||||
var schema = {}, globalAttributes, eventAttributes, blockContent;
|
||||
var schema = {}, globalAttributes, blockContent;
|
||||
var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
|
||||
|
||||
function add(name, attributes, children) {
|
||||
var ni, i, attributesOrder, args = arguments;
|
||||
|
||||
function arrayToMap(array) {
|
||||
function arrayToMap(array, obj) {
|
||||
var map = {}, i, l;
|
||||
|
||||
for (i = 0, l = array.length; i < l; i++) {
|
||||
map[array[i]] = {};
|
||||
map[array[i]] = obj || {};
|
||||
}
|
||||
|
||||
return map;
|
||||
@@ -59,13 +59,13 @@ define("tinymce/html/Schema", [
|
||||
children = children || [];
|
||||
attributes = attributes || "";
|
||||
|
||||
if (typeof(children) === "string") {
|
||||
if (typeof children === "string") {
|
||||
children = split(children);
|
||||
}
|
||||
|
||||
// Split string children
|
||||
for (i = 3; i < args.length; i++) {
|
||||
if (typeof(args[i]) === "string") {
|
||||
if (typeof args[i] === "string") {
|
||||
args[i] = split(args[i]);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ define("tinymce/html/Schema", [
|
||||
schema[name[ni]] = {
|
||||
attributes: arrayToMap(attributesOrder),
|
||||
attributesOrder: attributesOrder,
|
||||
children: arrayToMap(children)
|
||||
children: arrayToMap(children, dummyObj)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -108,13 +108,13 @@ define("tinymce/html/Schema", [
|
||||
globalAttributes = split("id accesskey class dir lang style tabindex title");
|
||||
|
||||
// Event attributes can be opt-in/opt-out
|
||||
eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
|
||||
/*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
|
||||
"ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
|
||||
"onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
|
||||
"onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
|
||||
"onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
|
||||
"onwaiting"
|
||||
);
|
||||
);*/
|
||||
|
||||
// Block content elements
|
||||
blockContent = split(
|
||||
@@ -185,7 +185,7 @@ define("tinymce/html/Schema", [
|
||||
add("a", "href target rel media hreflang type", phrasingContent);
|
||||
add("q", "cite", phrasingContent);
|
||||
add("ins del", "cite datetime", flowContent);
|
||||
add("img", "src alt usemap ismap width height");
|
||||
add("img", "src sizes srcset alt usemap ismap width height");
|
||||
add("iframe", "src name width height", flowContent);
|
||||
add("embed", "src type width height");
|
||||
add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
|
||||
@@ -222,9 +222,10 @@ define("tinymce/html/Schema", [
|
||||
add("mark rt rp summary bdi", "", phrasingContent);
|
||||
add("canvas", "width height", flowContent);
|
||||
add("video", "src crossorigin poster preload autoplay mediagroup loop " +
|
||||
"muted controls width height", flowContent, "track source");
|
||||
add("audio", "src crossorigin preload autoplay mediagroup loop muted controls", flowContent, "track source");
|
||||
add("source", "src type media");
|
||||
"muted controls width height buffered", flowContent, "track source");
|
||||
add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
|
||||
add("picture", "", "img source");
|
||||
add("source", "src srcset type media sizes");
|
||||
add("track", "kind src srclang label default");
|
||||
add("datalist", "", phrasingContent, "option");
|
||||
add("article section nav aside header footer", "", flowContent);
|
||||
@@ -244,7 +245,8 @@ define("tinymce/html/Schema", [
|
||||
if (type != "html5-strict") {
|
||||
addAttrs("script", "language xml:space");
|
||||
addAttrs("style", "xml:space");
|
||||
addAttrs("object", "declare classid codebase codetype archive standby align border hspace vspace");
|
||||
addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
|
||||
addAttrs("embed", "align name hspace vspace");
|
||||
addAttrs("param", "valuetype type");
|
||||
addAttrs("a", "charset name rev shape coords");
|
||||
addAttrs("br", "clear");
|
||||
@@ -282,7 +284,7 @@ define("tinymce/html/Schema", [
|
||||
addAttrs("input textarea", "placeholder");
|
||||
addAttrs("a", "download");
|
||||
addAttrs("link script img", "crossorigin");
|
||||
addAttrs("iframe", "srcdoc sandbox seamless allowfullscreen");
|
||||
addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
|
||||
}
|
||||
|
||||
// Special: iframe, ruby, video, audio, label
|
||||
@@ -313,6 +315,27 @@ define("tinymce/html/Schema", [
|
||||
return schema;
|
||||
}
|
||||
|
||||
function compileElementMap(value, mode) {
|
||||
var styles;
|
||||
|
||||
if (value) {
|
||||
styles = {};
|
||||
|
||||
if (typeof value == 'string') {
|
||||
value = {
|
||||
'*': value
|
||||
};
|
||||
}
|
||||
|
||||
// Convert styles into a rule list
|
||||
each(value, function(value, key) {
|
||||
styles[key] = styles[key.toUpperCase()] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
|
||||
});
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Schema instance.
|
||||
*
|
||||
@@ -321,9 +344,10 @@ define("tinymce/html/Schema", [
|
||||
* @param {Object} settings Name/value settings object.
|
||||
*/
|
||||
return function(settings) {
|
||||
var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
|
||||
var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap;
|
||||
var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, customElementsMap = {}, specialElements = {};
|
||||
var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
|
||||
var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
|
||||
var blockElementsMap, nonEmptyElementsMap, moveCaretBeforeOnEnterElementsMap, textBlockElementsMap, textInlineElementsMap;
|
||||
var customElementsMap = {}, specialElements = {};
|
||||
|
||||
// Creates an lookup table map object for the specified option or the default value
|
||||
function createLookupTable(option, default_value, extendWith) {
|
||||
@@ -341,7 +365,7 @@ define("tinymce/html/Schema", [
|
||||
}
|
||||
} else {
|
||||
// Create custom map
|
||||
value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
|
||||
value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -355,15 +379,9 @@ define("tinymce/html/Schema", [
|
||||
settings.valid_elements = '*[*]';
|
||||
}
|
||||
|
||||
// Build styles list
|
||||
if (settings.valid_styles) {
|
||||
validStyles = {};
|
||||
|
||||
// Convert styles into a rule list
|
||||
each(settings.valid_styles, function(value, key) {
|
||||
validStyles[key] = explode(value);
|
||||
});
|
||||
}
|
||||
validStyles = compileElementMap(settings.valid_styles);
|
||||
invalidStyles = compileElementMap(settings.invalid_styles, 'map');
|
||||
validClasses = compileElementMap(settings.valid_classes, 'map');
|
||||
|
||||
// Setup map objects
|
||||
whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
|
||||
@@ -372,15 +390,18 @@ define("tinymce/html/Schema", [
|
||||
'meta param embed source wbr track');
|
||||
boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
|
||||
'noshade nowrap readonly selected autoplay loop controls');
|
||||
nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
|
||||
nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
|
||||
moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', 'table', nonEmptyElementsMap);
|
||||
textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
|
||||
'blockquote center dir fieldset header footer article section hgroup aside nav figure');
|
||||
blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
|
||||
'th tr td li ol ul caption dl dt dd noscript menu isindex samp option ' +
|
||||
'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
|
||||
'datalist select optgroup', textBlockElementsMap);
|
||||
textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
|
||||
'dfn code mark q sup sub samp');
|
||||
|
||||
each((settings.special || 'script noscript style textarea').split(' '), function(name) {
|
||||
specialElements[name] = new RegExp('<\/' + name + '[^>]*>','gi');
|
||||
specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
|
||||
});
|
||||
|
||||
// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
|
||||
@@ -390,16 +411,16 @@ define("tinymce/html/Schema", [
|
||||
|
||||
// Parses the specified valid_elements string and adds to the current rules
|
||||
// This function is a bit hard to read since it's heavily optimized for speed
|
||||
function addValidElements(valid_elements) {
|
||||
function addValidElements(validElements) {
|
||||
var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
|
||||
prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
|
||||
elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
|
||||
attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
|
||||
hasPatternsRegExp = /[*?+]/;
|
||||
|
||||
if (valid_elements) {
|
||||
if (validElements) {
|
||||
// Split valid elements into an array with rules
|
||||
valid_elements = split(valid_elements, ',');
|
||||
validElements = split(validElements, ',');
|
||||
|
||||
if (elements['@']) {
|
||||
globalAttributes = elements['@'].attributes;
|
||||
@@ -407,9 +428,9 @@ define("tinymce/html/Schema", [
|
||||
}
|
||||
|
||||
// Loop all rules
|
||||
for (ei = 0, el = valid_elements.length; ei < el; ei++) {
|
||||
for (ei = 0, el = validElements.length; ei < el; ei++) {
|
||||
// Parse element rule
|
||||
matches = elementRuleRegExp.exec(valid_elements[ei]);
|
||||
matches = elementRuleRegExp.exec(validElements[ei]);
|
||||
if (matches) {
|
||||
// Setup local names for matches
|
||||
prefix = matches[1];
|
||||
@@ -539,11 +560,11 @@ define("tinymce/html/Schema", [
|
||||
}
|
||||
}
|
||||
|
||||
function setValidElements(valid_elements) {
|
||||
function setValidElements(validElements) {
|
||||
elements = {};
|
||||
patternElements = [];
|
||||
|
||||
addValidElements(valid_elements);
|
||||
addValidElements(validElements);
|
||||
|
||||
each(schemaItems, function(element, name) {
|
||||
children[name] = element.children;
|
||||
@@ -551,11 +572,14 @@ define("tinymce/html/Schema", [
|
||||
}
|
||||
|
||||
// Adds custom non HTML elements to the schema
|
||||
function addCustomElements(custom_elements) {
|
||||
function addCustomElements(customElements) {
|
||||
var customElementRegExp = /^(~)?(.+)$/;
|
||||
|
||||
if (custom_elements) {
|
||||
each(split(custom_elements, ','), function(rule) {
|
||||
if (customElements) {
|
||||
// Flush cached items since we are altering the default maps
|
||||
mapCache.text_block_elements = mapCache.block_elements = null;
|
||||
|
||||
each(split(customElements, ','), function(rule) {
|
||||
var matches = customElementRegExp.exec(rule),
|
||||
inline = matches[1] === '~',
|
||||
cloneName = inline ? 'span' : 'div',
|
||||
@@ -582,8 +606,9 @@ define("tinymce/html/Schema", [
|
||||
}
|
||||
|
||||
// Add custom elements at span/div positions
|
||||
each(children, function(element) {
|
||||
each(children, function(element, elmName) {
|
||||
if (element[cloneName]) {
|
||||
children[elmName] = element = extend({}, children[elmName]);
|
||||
element[name] = element[cloneName];
|
||||
}
|
||||
});
|
||||
@@ -592,11 +617,11 @@ define("tinymce/html/Schema", [
|
||||
}
|
||||
|
||||
// Adds valid children to the schema object
|
||||
function addValidChildren(valid_children) {
|
||||
function addValidChildren(validChildren) {
|
||||
var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
|
||||
|
||||
if (valid_children) {
|
||||
each(split(valid_children, ','), function(rule) {
|
||||
if (validChildren) {
|
||||
each(split(validChildren, ','), function(rule) {
|
||||
var matches = childRuleRegExp.exec(rule), parent, prefix;
|
||||
|
||||
if (matches) {
|
||||
@@ -613,6 +638,10 @@ define("tinymce/html/Schema", [
|
||||
|
||||
each(split(matches[3], '|'), function(child) {
|
||||
if (prefix === '-') {
|
||||
// Clone the element before we delete
|
||||
// things in it to not mess up default schemas
|
||||
children[matches[2]] = parent = extend({}, children[matches[2]]);
|
||||
|
||||
delete parent[child];
|
||||
} else {
|
||||
parent[child] = {};
|
||||
@@ -726,10 +755,32 @@ define("tinymce/html/Schema", [
|
||||
/**
|
||||
* Name/value map object with valid styles for each element.
|
||||
*
|
||||
* @field styles
|
||||
* @method getValidStyles
|
||||
* @type Object
|
||||
*/
|
||||
self.styles = validStyles;
|
||||
self.getValidStyles = function() {
|
||||
return validStyles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Name/value map object with valid styles for each element.
|
||||
*
|
||||
* @method getInvalidStyles
|
||||
* @type Object
|
||||
*/
|
||||
self.getInvalidStyles = function() {
|
||||
return invalidStyles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Name/value map object with valid classes for each element.
|
||||
*
|
||||
* @method getValidClasses
|
||||
* @type Object
|
||||
*/
|
||||
self.getValidClasses = function() {
|
||||
return validClasses;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a map with boolean attributes.
|
||||
@@ -761,6 +812,16 @@ define("tinymce/html/Schema", [
|
||||
return textBlockElementsMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a map of inline text format nodes for example strong/span or ins.
|
||||
*
|
||||
* @method getTextInlineElements
|
||||
* @return {Object} Name/value lookup map for text format elements.
|
||||
*/
|
||||
self.getTextInlineElements = function() {
|
||||
return textInlineElementsMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a map with short ended elements such as BR or IMG.
|
||||
*
|
||||
@@ -792,6 +853,17 @@ define("tinymce/html/Schema", [
|
||||
return nonEmptyElementsMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a map with elements that the caret should be moved in front of after enter is
|
||||
* pressed
|
||||
*
|
||||
* @method getMoveCaretBeforeOnEnterElements
|
||||
* @return {Object} Name/value lookup map for elements to place the caret in front of.
|
||||
*/
|
||||
self.getMoveCaretBeforeOnEnterElements = function() {
|
||||
return moveCaretBeforeOnEnterElementsMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
|
||||
*
|
||||
@@ -825,6 +897,7 @@ define("tinymce/html/Schema", [
|
||||
*/
|
||||
self.isValidChild = function(name, child) {
|
||||
var parent = children[name];
|
||||
|
||||
return !!(parent && parent[child]);
|
||||
};
|
||||
|
||||
@@ -889,7 +962,7 @@ define("tinymce/html/Schema", [
|
||||
|
||||
/**
|
||||
* Parses a valid elements string and adds it to the schema. The valid elements
|
||||
format is for example "element[attr=default|otherattr]".
|
||||
* format is for example "element[attr=default|otherattr]".
|
||||
* Existing rules will be replaced with the ones specified, so this extends the schema.
|
||||
*
|
||||
* @method addValidElements
|
||||
|
||||
Regular → Executable
+1
-1
@@ -27,7 +27,7 @@ define("tinymce/html/Serializer", [
|
||||
* @constructor
|
||||
* @method Serializer
|
||||
* @param {Object} settings Name/value settings object.
|
||||
* @param {tinymce.html.Schema} schema Schema instance to use. schema是一些策略, 有些合法, 一些不合法的
|
||||
* @param {tinymce.html.Schema} schema Schema instance to use.
|
||||
*/
|
||||
return function(settings, schema) {
|
||||
var self = this, writer = new Writer(settings);
|
||||
|
||||
Regular → Executable
+75
-26
@@ -29,14 +29,20 @@
|
||||
define("tinymce/html/Styles", [], function() {
|
||||
return function(settings, schema) {
|
||||
/*jshint maxlen:255 */
|
||||
/*eslint max-len:0 */
|
||||
var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
|
||||
urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
|
||||
styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
|
||||
trimRightRegExp = /\s+$/,
|
||||
undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF';
|
||||
undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
|
||||
|
||||
settings = settings || {};
|
||||
|
||||
if (schema) {
|
||||
validStyles = schema.getValidStyles();
|
||||
invalidStyles = schema.getInvalidStyles();
|
||||
}
|
||||
|
||||
encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
|
||||
for (i = 0; i < encodingItems.length; i++) {
|
||||
encodingLookup[encodingItems[i]] = invisibleChar + i;
|
||||
@@ -78,38 +84,42 @@ define("tinymce/html/Styles", [], function() {
|
||||
var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
|
||||
var urlConverterScope = settings.url_converter_scope || this;
|
||||
|
||||
function compress(prefix, suffix) {
|
||||
function compress(prefix, suffix, noJoin) {
|
||||
var top, right, bottom, left;
|
||||
|
||||
// IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
|
||||
// So lets asume it shouldn't be there
|
||||
if (styles['border-image'] === 'none') {
|
||||
delete styles['border-image'];
|
||||
}
|
||||
|
||||
// Get values and check it it needs compressing
|
||||
top = styles[prefix + '-top' + suffix];
|
||||
if (!top) {
|
||||
return;
|
||||
}
|
||||
|
||||
right = styles[prefix + '-right' + suffix];
|
||||
if (top != right) {
|
||||
if (!right) {
|
||||
return;
|
||||
}
|
||||
|
||||
bottom = styles[prefix + '-bottom' + suffix];
|
||||
if (right != bottom) {
|
||||
if (!bottom) {
|
||||
return;
|
||||
}
|
||||
|
||||
left = styles[prefix + '-left' + suffix];
|
||||
if (bottom != left) {
|
||||
if (!left) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compress
|
||||
styles[prefix + suffix] = left;
|
||||
var box = [top, right, bottom, left];
|
||||
i = box.length - 1;
|
||||
while (i--) {
|
||||
if (box[i] !== box[i + 1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > -1 && noJoin) {
|
||||
return;
|
||||
}
|
||||
|
||||
styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
|
||||
delete styles[prefix + '-top' + suffix];
|
||||
delete styles[prefix + '-right' + suffix];
|
||||
delete styles[prefix + '-bottom' + suffix];
|
||||
@@ -122,7 +132,7 @@ define("tinymce/html/Styles", [], function() {
|
||||
function canCompress(key) {
|
||||
var value = styles[key], i;
|
||||
|
||||
if (!value || value.indexOf(' ') < 0) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,8 +207,16 @@ define("tinymce/html/Styles", [], function() {
|
||||
|
||||
url = decode(url || url2 || url3);
|
||||
|
||||
if (!settings.allow_script_urls && /(java|vb)script:/i.test(url.replace(/[\s\r\n]+/, ''))) {
|
||||
return "";
|
||||
if (!settings.allow_script_urls) {
|
||||
var scriptUrl = url.replace(/[\s\r\n]+/, '');
|
||||
|
||||
if (/(java|vb)script:/i.test(scriptUrl)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the URL to relative/absolute depending on config
|
||||
@@ -223,8 +241,16 @@ define("tinymce/html/Styles", [], function() {
|
||||
name = matches[1].replace(trimRightRegExp, '').toLowerCase();
|
||||
value = matches[2].replace(trimRightRegExp, '');
|
||||
|
||||
// Decode escaped sequences like \65 -> e
|
||||
/*jshint loopfunc:true*/
|
||||
/*eslint no-loop-func:0 */
|
||||
value = value.replace(/\\[0-9a-f]+/g, function(e) {
|
||||
return String.fromCharCode(parseInt(e.substr(1), 16));
|
||||
});
|
||||
|
||||
if (name && value.length > 0) {
|
||||
if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(/.test(value))) {
|
||||
// Don't allow behavior name or expression/comments within the values
|
||||
if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -245,9 +271,8 @@ define("tinymce/html/Styles", [], function() {
|
||||
|
||||
styleRegExp.lastIndex = matches.index + matches[0].length;
|
||||
}
|
||||
|
||||
// Compress the styles to reduce it's size for example IE will expand styles
|
||||
compress("border", "");
|
||||
compress("border", "", true);
|
||||
compress("border", "-width");
|
||||
compress("border", "-color");
|
||||
compress("border", "-style");
|
||||
@@ -259,6 +284,12 @@ define("tinymce/html/Styles", [], function() {
|
||||
if (styles.border === 'medium none') {
|
||||
delete styles.border;
|
||||
}
|
||||
|
||||
// IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
|
||||
// So lets asume it shouldn't be there
|
||||
if (styles['border-image'] === 'none') {
|
||||
delete styles['border-image'];
|
||||
}
|
||||
}
|
||||
|
||||
return styles;
|
||||
@@ -269,16 +300,16 @@ define("tinymce/html/Styles", [], function() {
|
||||
*
|
||||
* @method serialize
|
||||
* @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
|
||||
* @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
|
||||
* @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
|
||||
* @return {String} String representation of the style object for example: border: 1px solid red.
|
||||
*/
|
||||
serialize: function(styles, element_name) {
|
||||
serialize: function(styles, elementName) {
|
||||
var css = '', name, value;
|
||||
|
||||
function serializeStyles(name) {
|
||||
var styleList, i, l, value;
|
||||
|
||||
styleList = schema.styles[name];
|
||||
styleList = validStyles[name];
|
||||
if (styleList) {
|
||||
for (i = 0, l = styleList.length; i < l; i++) {
|
||||
name = styleList[i];
|
||||
@@ -291,18 +322,36 @@ define("tinymce/html/Styles", [], function() {
|
||||
}
|
||||
}
|
||||
|
||||
function isValid(name, elementName) {
|
||||
var styleMap;
|
||||
|
||||
styleMap = invalidStyles['*'];
|
||||
if (styleMap && styleMap[name]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
styleMap = invalidStyles[elementName];
|
||||
if (styleMap && styleMap[name]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Serialize styles according to schema
|
||||
if (element_name && schema && schema.styles) {
|
||||
if (elementName && validStyles) {
|
||||
// Serialize global styles and element specific styles
|
||||
serializeStyles('*');
|
||||
serializeStyles(element_name);
|
||||
serializeStyles(elementName);
|
||||
} else {
|
||||
// Output the styles in the order they are inside the object
|
||||
for (name in styles) {
|
||||
value = styles[name];
|
||||
|
||||
if (value !== undef && value.length > 0) {
|
||||
css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
|
||||
if (!invalidStyles || isValid(name, elementName)) {
|
||||
css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+31
-17
@@ -13,6 +13,7 @@
|
||||
(function($) {
|
||||
var undef,
|
||||
lazyLoading,
|
||||
patchApplied,
|
||||
delayedInits = [],
|
||||
win = window;
|
||||
|
||||
@@ -26,7 +27,7 @@
|
||||
|
||||
// Get editor instance
|
||||
if (!settings) {
|
||||
return tinymce.get(self[0].id);
|
||||
return window.tinymce ? tinymce.get(self[0].id) : null;
|
||||
}
|
||||
|
||||
self.css('visibility', 'hidden'); // Hide textarea to avoid flicker
|
||||
@@ -35,9 +36,9 @@
|
||||
var editors = [], initCount = 0;
|
||||
|
||||
// Apply patches to the jQuery object, only once
|
||||
if (applyPatch) {
|
||||
if (!patchApplied) {
|
||||
applyPatch();
|
||||
applyPatch = null;
|
||||
patchApplied = true;
|
||||
}
|
||||
|
||||
// Create an editor instance for each matched node
|
||||
@@ -69,7 +70,7 @@
|
||||
if (oninit) {
|
||||
// Fire the oninit event ones each editor instance is initialized
|
||||
if (++initCount == editors.length) {
|
||||
if (typeof(func) === "string") {
|
||||
if (typeof func === "string") {
|
||||
scope = (func.indexOf(".") === -1) ? null : tinymce.resolve(func.replace(/\.\w+$/, ""));
|
||||
func = tinymce.resolve(func);
|
||||
}
|
||||
@@ -111,7 +112,8 @@
|
||||
if (url.indexOf('gzip') != -1) {
|
||||
lang = settings.language || "en";
|
||||
url = url + (/\?/.test(url) ? '&' : '?') + "js=true&core=true&suffix=" + escape(suffix) +
|
||||
"&themes=" + escape(settings.theme || '') + "&plugins=" + escape(settings.plugins || '') + "&languages=" + (lang || '');
|
||||
"&themes=" + escape(settings.theme || 'modern') + "&plugins=" +
|
||||
escape(settings.plugins || '') + "&languages=" + (lang || '');
|
||||
|
||||
// Check if compressor script is already loaded otherwise setup a basic one
|
||||
if (!win.tinyMCE_GZ) {
|
||||
@@ -146,7 +148,7 @@
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.onload = script.onreadystatechange = function(e) {
|
||||
e = e || event;
|
||||
e = e || window.event;
|
||||
|
||||
if (lazyLoading !== 2 && (e.type == 'load' || /complete|loaded/.test(script.readyState))) {
|
||||
tinymce.dom.Event.domLoaded = 1;
|
||||
@@ -182,7 +184,17 @@
|
||||
// it's now possible to use things like $('*:tinymce') to get all TinyMCE bound elements.
|
||||
$.extend($.expr[":"], {
|
||||
tinymce: function(e) {
|
||||
return !!(e.id && "tinymce" in window && tinymce.get(e.id));
|
||||
var editor;
|
||||
|
||||
if (e.id && "tinymce" in window) {
|
||||
editor = tinymce.get(e.id);
|
||||
|
||||
if (editor && editor.editorManager === tinymce) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -264,7 +276,7 @@
|
||||
var origFn = jQueryFn[name] = $.fn[name],
|
||||
textProc = (name === "text");
|
||||
|
||||
$.fn[name] = function(value) {
|
||||
$.fn[name] = function(value) {
|
||||
var self = this;
|
||||
|
||||
if (!containsTinyMCE(self)) {
|
||||
@@ -292,7 +304,7 @@
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// Makes it possible to use $('#id').append("content"); to append contents to the TinyMCE editor iframe
|
||||
@@ -300,7 +312,7 @@
|
||||
var origFn = jQueryFn[name] = $.fn[name],
|
||||
prepend = (name === "prepend");
|
||||
|
||||
$.fn[name] = function(value) {
|
||||
$.fn[name] = function(value) {
|
||||
var self = this;
|
||||
|
||||
if (!containsTinyMCE(self)) {
|
||||
@@ -308,19 +320,21 @@
|
||||
}
|
||||
|
||||
if (value !== undef) {
|
||||
self.filter(":tinymce").each(function(i, node) {
|
||||
var ed = tinyMCEInstance(node);
|
||||
if (typeof value === "string") {
|
||||
self.filter(":tinymce").each(function(i, node) {
|
||||
var ed = tinyMCEInstance(node);
|
||||
|
||||
if (ed) {
|
||||
ed.setContent(prepend ? value + ed.getContent() : ed.getContent() + value);
|
||||
}
|
||||
});
|
||||
if (ed) {
|
||||
ed.setContent(prepend ? value + ed.getContent() : ed.getContent() + value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
origFn.apply(self.not(":tinymce"), arguments);
|
||||
|
||||
return self; // return original set for chaining
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// Makes sure that the editor instance gets properly destroyed when the parent element is removed
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+70
-4
@@ -61,6 +61,43 @@ define("tinymce/ui/Button", [
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets/gets the current button icon.
|
||||
*
|
||||
* @method icon
|
||||
* @param {String} [icon] New icon identifier.
|
||||
* @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
|
||||
*/
|
||||
icon: function(icon) {
|
||||
var self = this, prefix = self.classPrefix;
|
||||
|
||||
if (typeof icon == 'undefined') {
|
||||
return self.settings.icon;
|
||||
}
|
||||
|
||||
self.settings.icon = icon;
|
||||
icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
|
||||
|
||||
if (self._rendered) {
|
||||
var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
|
||||
|
||||
if (icon) {
|
||||
if (!iconElm || iconElm != btnElm.firstChild) {
|
||||
iconElm = document.createElement('i');
|
||||
btnElm.insertBefore(iconElm, btnElm.firstChild);
|
||||
}
|
||||
|
||||
iconElm.className = icon;
|
||||
} else if (iconElm) {
|
||||
btnElm.removeChild(iconElm);
|
||||
}
|
||||
|
||||
self.text(self._text); // Set text again to fix whitespace between icon + text
|
||||
}
|
||||
|
||||
return self;
|
||||
},
|
||||
|
||||
/**
|
||||
* Repaints the button for example after it's been resizes by a layout engine.
|
||||
*
|
||||
@@ -74,6 +111,26 @@ define("tinymce/ui/Button", [
|
||||
this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets/gets the current button text.
|
||||
*
|
||||
* @method text
|
||||
* @param {String} [text] New button text.
|
||||
* @return {String|tinymce.ui.Button} Current text or current Button instance.
|
||||
*/
|
||||
text: function(text) {
|
||||
var self = this;
|
||||
|
||||
if (self._rendered) {
|
||||
var textNode = self.getEl().lastChild.lastChild;
|
||||
if (textNode) {
|
||||
textNode.data = self.translate(text);
|
||||
}
|
||||
}
|
||||
|
||||
return self._super(text);
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the control as a HTML string.
|
||||
*
|
||||
@@ -82,17 +139,26 @@ define("tinymce/ui/Button", [
|
||||
*/
|
||||
renderHtml: function() {
|
||||
var self = this, id = self._id, prefix = self.classPrefix;
|
||||
var icon = self.settings.icon, image = '';
|
||||
var icon = self.settings.icon, image;
|
||||
|
||||
if (self.settings.image) {
|
||||
image = self.settings.image;
|
||||
if (image) {
|
||||
icon = 'none';
|
||||
image = ' style="background-image: url(\'' + self.settings.image + '\')"';
|
||||
|
||||
// Support for [high dpi, low dpi] image sources
|
||||
if (typeof image != "string") {
|
||||
image = window.getSelection ? image[0] : image[1];
|
||||
}
|
||||
|
||||
image = ' style="background-image: url(\'' + image + '\')"';
|
||||
} else {
|
||||
image = '';
|
||||
}
|
||||
|
||||
icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
|
||||
'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
|
||||
'<button role="presentation" type="button" tabindex="-1">' +
|
||||
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
|
||||
(self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
|
||||
|
||||
Regular → Executable
+3
-3
@@ -34,7 +34,7 @@ define("tinymce/ui/ButtonGroup", [
|
||||
return Container.extend({
|
||||
Defaults: {
|
||||
defaultType: 'button',
|
||||
role: 'toolbar'
|
||||
role: 'group'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -51,8 +51,8 @@ define("tinymce/ui/ButtonGroup", [
|
||||
layout.preRender(self);
|
||||
|
||||
return (
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '">'+
|
||||
'<div id="' + self._id + '-body">'+
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '">' +
|
||||
'<div id="' + self._id + '-body">' +
|
||||
(self.settings.html || '') + layout.renderHtml(self) +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
|
||||
Regular → Executable
+2
-2
@@ -109,9 +109,9 @@ define("tinymce/ui/Checkbox", [
|
||||
var self = this, id = self._id, prefix = self.classPrefix;
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labeledby="' + id + '-al" tabindex="-1">' +
|
||||
'<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
|
||||
'<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
|
||||
'<span id="' + id +'-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
|
||||
'<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
Regular → Executable
+1
-1
@@ -105,7 +105,7 @@ define("tinymce/ui/Collection", [
|
||||
var self = this, i, l, matches = [], item, match;
|
||||
|
||||
// Compile string into selector expression
|
||||
if (typeof(selector) === "string") {
|
||||
if (typeof selector === "string") {
|
||||
selector = new Selector(selector);
|
||||
|
||||
match = function(item) {
|
||||
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* ColorBox.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This widget lets you enter colors and browse for colors by pressing the color button. It also displays
|
||||
* a preview of the current color.
|
||||
*
|
||||
* @-x-less ColorBox.less
|
||||
* @class tinymce.ui.ColorBox
|
||||
* @extends tinymce.ui.ComboBox
|
||||
*/
|
||||
define("tinymce/ui/ColorBox", [
|
||||
"tinymce/ui/ComboBox"
|
||||
], function(ComboBox) {
|
||||
"use strict";
|
||||
|
||||
return ComboBox.extend({
|
||||
/**
|
||||
* Constructs a new control instance with the specified settings.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} settings Name/value object with settings.
|
||||
*/
|
||||
init: function(settings) {
|
||||
var self = this;
|
||||
|
||||
settings.spellcheck = false;
|
||||
|
||||
if (settings.onaction) {
|
||||
settings.icon = 'none';
|
||||
}
|
||||
|
||||
self._super(settings);
|
||||
|
||||
self.addClass('colorbox');
|
||||
self.on('change keyup postrender', function() {
|
||||
self.repaintColor(self.value());
|
||||
});
|
||||
},
|
||||
|
||||
repaintColor: function(value) {
|
||||
var elm = this.getEl().getElementsByTagName('i')[0];
|
||||
|
||||
if (elm) {
|
||||
try {
|
||||
elm.style.background = value;
|
||||
} catch (ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
value: function(value) {
|
||||
var self = this;
|
||||
|
||||
if (typeof value != "undefined") {
|
||||
if (self._rendered) {
|
||||
self.repaintColor(value);
|
||||
}
|
||||
}
|
||||
|
||||
return self._super(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
Regular → Executable
+22
-8
@@ -10,7 +10,7 @@
|
||||
|
||||
/**
|
||||
* This class creates a color button control. This is a split button in which the main
|
||||
* button has a visual representation of the currently selected color. When clicked
|
||||
* button has a visual representation of the currently selected color. When clicked
|
||||
* the caret button displays a color picker, allowing the user to select a new color.
|
||||
*
|
||||
* @-x-less ColorButton.less
|
||||
@@ -22,7 +22,7 @@ define("tinymce/ui/ColorButton", [
|
||||
"tinymce/dom/DOMUtils"
|
||||
], function(PanelButton, DomUtils) {
|
||||
"use strict";
|
||||
|
||||
|
||||
var DOM = DomUtils.DOM;
|
||||
|
||||
return PanelButton.extend({
|
||||
@@ -54,6 +54,18 @@ define("tinymce/ui/ColorButton", [
|
||||
return this._color;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the current color.
|
||||
*
|
||||
* @method resetColor
|
||||
* @return {tinymce.ui.ColorButton} Current instance.
|
||||
*/
|
||||
resetColor: function() {
|
||||
this._color = null;
|
||||
this.getEl('preview').style.backgroundColor = null;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the control as a HTML string.
|
||||
*
|
||||
@@ -66,22 +78,21 @@ define("tinymce/ui/ColorButton", [
|
||||
var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '">' +
|
||||
'<button role="presentation" hidefocus type="button" tabindex="-1">' +
|
||||
'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
|
||||
'<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
|
||||
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
|
||||
'<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
|
||||
(self._text ? (icon ? ' ' : '') + (self._text) : '') +
|
||||
'</button>' +
|
||||
'<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' +
|
||||
'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
|
||||
' <i class="' + prefix + 'caret"></i>' +
|
||||
'</button>' +
|
||||
'</div>'
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Called after the control has been rendered.
|
||||
* 这里绑定事件
|
||||
*
|
||||
* @method postRender
|
||||
*/
|
||||
@@ -89,6 +100,10 @@ define("tinymce/ui/ColorButton", [
|
||||
var self = this, onClickHandler = self.settings.onclick;
|
||||
|
||||
self.on('click', function(e) {
|
||||
if (e.aria && e.aria.key == 'down') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
|
||||
e.stopImmediatePropagation();
|
||||
onClickHandler.call(self, e);
|
||||
@@ -99,6 +114,5 @@ define("tinymce/ui/ColorButton", [
|
||||
|
||||
return self._super();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
Executable
+206
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* ColorPicker.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Color picker widget lets you select colors.
|
||||
*
|
||||
* @-x-less ColorPicker.less
|
||||
* @class tinymce.ui.ColorPicker
|
||||
* @extends tinymce.ui.Widget
|
||||
*/
|
||||
define("tinymce/ui/ColorPicker", [
|
||||
"tinymce/ui/Widget",
|
||||
"tinymce/ui/DragHelper",
|
||||
"tinymce/ui/DomUtils",
|
||||
"tinymce/util/Color"
|
||||
], function(Widget, DragHelper, DomUtils, Color) {
|
||||
"use strict";
|
||||
|
||||
return Widget.extend({
|
||||
Defaults: {
|
||||
classes: "widget colorpicker"
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructs a new colorpicker instance with the specified settings.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} settings Name/value object with settings.
|
||||
* @setting {String} color Initial color value.
|
||||
*/
|
||||
init: function(settings) {
|
||||
this._super(settings);
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm;
|
||||
|
||||
hueRootElm = self.getEl('h');
|
||||
huePointElm = self.getEl('hp');
|
||||
svRootElm = self.getEl('sv');
|
||||
svPointElm = self.getEl('svp');
|
||||
|
||||
function getPos(elm, event) {
|
||||
var pos = DomUtils.getPos(elm), x, y;
|
||||
|
||||
x = event.pageX - pos.x;
|
||||
y = event.pageY - pos.y;
|
||||
|
||||
x = Math.max(0, Math.min(x / elm.clientWidth, 1));
|
||||
y = Math.max(0, Math.min(y / elm.clientHeight, 1));
|
||||
|
||||
return {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
|
||||
function updateColor(hsv, hueUpdate) {
|
||||
var hue = (360 - hsv.h) / 360;
|
||||
|
||||
DomUtils.css(huePointElm, {
|
||||
top: (hue * 100) + '%'
|
||||
});
|
||||
|
||||
if (!hueUpdate) {
|
||||
DomUtils.css(svPointElm, {
|
||||
left: hsv.s + '%',
|
||||
top: (100 - hsv.v) + '%'
|
||||
});
|
||||
}
|
||||
|
||||
svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex();
|
||||
self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h});
|
||||
}
|
||||
|
||||
function updateSaturationAndValue(e) {
|
||||
var pos;
|
||||
|
||||
pos = getPos(svRootElm, e);
|
||||
hsv.s = pos.x * 100;
|
||||
hsv.v = (1 - pos.y) * 100;
|
||||
|
||||
updateColor(hsv);
|
||||
self.fire('change');
|
||||
}
|
||||
|
||||
function updateHue(e) {
|
||||
var pos;
|
||||
|
||||
pos = getPos(hueRootElm, e);
|
||||
hsv = color.toHsv();
|
||||
hsv.h = (1 - pos.y) * 360;
|
||||
updateColor(hsv, true);
|
||||
self.fire('change');
|
||||
}
|
||||
|
||||
self._repaint = function() {
|
||||
hsv = color.toHsv();
|
||||
updateColor(hsv);
|
||||
};
|
||||
|
||||
self._super();
|
||||
|
||||
self._svdraghelper = new DragHelper(self._id + '-sv', {
|
||||
start: updateSaturationAndValue,
|
||||
drag: updateSaturationAndValue
|
||||
});
|
||||
|
||||
self._hdraghelper = new DragHelper(self._id + '-h', {
|
||||
start: updateHue,
|
||||
drag: updateHue
|
||||
});
|
||||
|
||||
self._repaint();
|
||||
},
|
||||
|
||||
rgb: function() {
|
||||
return this.color().toRgb();
|
||||
},
|
||||
|
||||
value: function(value) {
|
||||
var self = this;
|
||||
|
||||
if (arguments.length) {
|
||||
self.color().parse(value);
|
||||
|
||||
if (self._rendered) {
|
||||
self._repaint();
|
||||
}
|
||||
} else {
|
||||
return self.color().toHex();
|
||||
}
|
||||
},
|
||||
|
||||
color: function() {
|
||||
if (!this._color) {
|
||||
this._color = new Color();
|
||||
}
|
||||
|
||||
return this._color;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the control as a HTML string.
|
||||
*
|
||||
* @method renderHtml
|
||||
* @return {String} HTML representing the control.
|
||||
*/
|
||||
renderHtml: function() {
|
||||
var self = this, id = self._id, prefix = self.classPrefix, hueHtml;
|
||||
var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000';
|
||||
|
||||
function getOldIeFallbackHtml() {
|
||||
var i, l, html = '', gradientPrefix, stopsList;
|
||||
|
||||
gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=';
|
||||
stopsList = stops.split(',');
|
||||
for (i = 0, l = stopsList.length - 1; i < l; i++) {
|
||||
html += (
|
||||
'<div class="' + prefix + 'colorpicker-h-chunk" style="' +
|
||||
'height:' + (100 / l) + '%;' +
|
||||
gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' +
|
||||
'-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' +
|
||||
'"></div>'
|
||||
);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
var gradientCssText = (
|
||||
'background: -ms-linear-gradient(top,' + stops + ');' +
|
||||
'background: linear-gradient(to bottom,' + stops + ');'
|
||||
);
|
||||
|
||||
hueHtml = (
|
||||
'<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' +
|
||||
getOldIeFallbackHtml() +
|
||||
'<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '">' +
|
||||
'<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' +
|
||||
'<div class="' + prefix + 'colorpicker-overlay1">' +
|
||||
'<div class="' + prefix + 'colorpicker-overlay2">' +
|
||||
'<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' +
|
||||
'<div class="' + prefix + 'colorpicker-selector2"></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
hueHtml +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Regular → Executable
+102
-11
@@ -18,8 +18,9 @@
|
||||
*/
|
||||
define("tinymce/ui/ComboBox", [
|
||||
"tinymce/ui/Widget",
|
||||
"tinymce/ui/Factory",
|
||||
"tinymce/ui/DomUtils"
|
||||
], function(Widget, DomUtils) {
|
||||
], function(Widget, Factory, DomUtils) {
|
||||
"use strict";
|
||||
|
||||
return Widget.extend({
|
||||
@@ -35,13 +36,30 @@ define("tinymce/ui/ComboBox", [
|
||||
|
||||
self._super(settings);
|
||||
self.addClass('combobox');
|
||||
self.subinput = true;
|
||||
self.ariaTarget = 'inp'; // TODO: Figure out a better way
|
||||
|
||||
settings = self.settings;
|
||||
settings.menu = settings.menu || settings.values;
|
||||
|
||||
if (settings.menu) {
|
||||
settings.icon = 'caret';
|
||||
}
|
||||
|
||||
self.on('click', function(e) {
|
||||
var elm = e.target;
|
||||
var elm = e.target, root = self.getEl();
|
||||
|
||||
while (elm) {
|
||||
while (elm && elm != root) {
|
||||
if (elm.id && elm.id.indexOf('-open') != -1) {
|
||||
self.fire('action');
|
||||
|
||||
if (settings.menu) {
|
||||
self.showMenu();
|
||||
|
||||
if (e.aria) {
|
||||
self.menu.items()[0].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elm = elm.parentNode;
|
||||
@@ -90,6 +108,55 @@ define("tinymce/ui/ComboBox", [
|
||||
}
|
||||
},
|
||||
|
||||
showMenu: function() {
|
||||
var self = this, settings = self.settings, menu;
|
||||
|
||||
if (!self.menu) {
|
||||
menu = settings.menu || [];
|
||||
|
||||
// Is menu array then auto constuct menu control
|
||||
if (menu.length) {
|
||||
menu = {
|
||||
type: 'menu',
|
||||
items: menu
|
||||
};
|
||||
} else {
|
||||
menu.type = menu.type || 'menu';
|
||||
}
|
||||
|
||||
self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
|
||||
self.fire('createmenu');
|
||||
self.menu.reflow();
|
||||
self.menu.on('cancel', function(e) {
|
||||
if (e.control === self.menu) {
|
||||
self.focus();
|
||||
}
|
||||
});
|
||||
|
||||
self.menu.on('show hide', function(e) {
|
||||
e.control.items().each(function(ctrl) {
|
||||
ctrl.active(ctrl.value() == self.value());
|
||||
});
|
||||
}).fire('show');
|
||||
|
||||
self.menu.on('select', function(e) {
|
||||
self.value(e.control.value());
|
||||
});
|
||||
|
||||
self.on('focusin', function(e) {
|
||||
if (e.target.tagName.toUpperCase() == 'INPUT') {
|
||||
self.menu.hide();
|
||||
}
|
||||
});
|
||||
|
||||
self.aria('expanded', true);
|
||||
}
|
||||
|
||||
self.menu.show();
|
||||
self.menu.layoutRect({w: self.layoutRect().w});
|
||||
self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter/setter function for the control value.
|
||||
*
|
||||
@@ -100,7 +167,7 @@ define("tinymce/ui/ComboBox", [
|
||||
value: function(value) {
|
||||
var self = this;
|
||||
|
||||
if (typeof(value) != "undefined") {
|
||||
if (typeof value != "undefined") {
|
||||
self._value = value;
|
||||
self.removeClass('placeholder');
|
||||
|
||||
@@ -134,7 +201,7 @@ define("tinymce/ui/ComboBox", [
|
||||
disabled: function(state) {
|
||||
var self = this;
|
||||
|
||||
if (self._rendered && typeof(state) != 'undefined') {
|
||||
if (self._rendered && typeof state != 'undefined') {
|
||||
self.getEl('inp').disabled = state;
|
||||
}
|
||||
|
||||
@@ -211,16 +278,40 @@ define("tinymce/ui/ComboBox", [
|
||||
renderHtml: function() {
|
||||
var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
|
||||
var value = settings.value || settings.placeholder || '';
|
||||
var icon, text, openBtnHtml = '';
|
||||
var icon, text, openBtnHtml = '', extraAttrs = '';
|
||||
|
||||
if ("spellcheck" in settings) {
|
||||
extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
|
||||
}
|
||||
|
||||
if (settings.maxLength) {
|
||||
extraAttrs += ' maxlength="' + settings.maxLength + '"';
|
||||
}
|
||||
|
||||
if (settings.size) {
|
||||
extraAttrs += ' size="' + settings.size + '"';
|
||||
}
|
||||
|
||||
if (settings.subtype) {
|
||||
extraAttrs += ' type="' + settings.subtype + '"';
|
||||
}
|
||||
|
||||
if (self.disabled()) {
|
||||
extraAttrs += ' disabled="disabled"';
|
||||
}
|
||||
|
||||
icon = settings.icon;
|
||||
if (icon && icon != 'caret') {
|
||||
icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
|
||||
}
|
||||
|
||||
icon = settings.icon ? prefix + 'ico ' + prefix + 'i-' + settings.icon : '';
|
||||
text = self._text;
|
||||
|
||||
if (icon || text) {
|
||||
openBtnHtml = (
|
||||
'<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1">' +
|
||||
'<button id="' + id + '-action" type="button" hidefocus tabindex="-1">' +
|
||||
(icon ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
|
||||
'<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
|
||||
'<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
|
||||
(icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
|
||||
(text ? (icon ? ' ' : '') + text : '') +
|
||||
'</button>' +
|
||||
'</div>'
|
||||
@@ -232,7 +323,7 @@ define("tinymce/ui/ComboBox", [
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '">' +
|
||||
'<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
|
||||
value + '" hidefocus="true"' + (self.disabled() ? ' disabled="disabled"' : '') + '>' +
|
||||
value + '" hidefocus="1"' + extraAttrs + ' />' +
|
||||
openBtnHtml +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
Regular → Executable
+47
-14
@@ -22,9 +22,10 @@ define("tinymce/ui/Container", [
|
||||
"tinymce/ui/Collection",
|
||||
"tinymce/ui/Selector",
|
||||
"tinymce/ui/Factory",
|
||||
"tinymce/ui/KeyboardNavigation",
|
||||
"tinymce/util/Tools",
|
||||
"tinymce/ui/DomUtils"
|
||||
], function(Control, Collection, Selector, Factory, Tools, DomUtils) {
|
||||
], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
|
||||
"use strict";
|
||||
|
||||
var selectorCache = {};
|
||||
@@ -115,15 +116,41 @@ define("tinymce/ui/Container", [
|
||||
* for the first control in the container and focus that.
|
||||
*
|
||||
* @method focus
|
||||
* @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
|
||||
* @return {tinymce.ui.Collection} Current instance.
|
||||
*/
|
||||
focus: function() {
|
||||
var self = this;
|
||||
focus: function(keyboard) {
|
||||
var self = this, focusCtrl, keyboardNav, items;
|
||||
|
||||
if (self.keyNav) {
|
||||
self.keyNav.focusFirst();
|
||||
} else {
|
||||
self._super();
|
||||
if (keyboard) {
|
||||
keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
|
||||
|
||||
if (keyboardNav) {
|
||||
keyboardNav.focusFirst(self);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
items = self.find('*');
|
||||
|
||||
// TODO: Figure out a better way to auto focus alert dialog buttons
|
||||
if (self.statusbar) {
|
||||
items.add(self.statusbar.items());
|
||||
}
|
||||
|
||||
items.each(function(ctrl) {
|
||||
if (ctrl.settings.autofocus) {
|
||||
focusCtrl = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctrl.canFocus) {
|
||||
focusCtrl = focusCtrl || ctrl;
|
||||
}
|
||||
});
|
||||
|
||||
if (focusCtrl) {
|
||||
focusCtrl.focus();
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -187,7 +214,7 @@ define("tinymce/ui/Container", [
|
||||
// Construct item if needed
|
||||
if (!(item instanceof Control)) {
|
||||
// Name only then convert it to an object
|
||||
if (typeof(item) == "string") {
|
||||
if (typeof item == "string") {
|
||||
item = {type: item};
|
||||
}
|
||||
|
||||
@@ -325,7 +352,7 @@ define("tinymce/ui/Container", [
|
||||
self.find('*').each(function(ctrl) {
|
||||
var name = ctrl.name(), value = ctrl.value();
|
||||
|
||||
if (name && typeof(value) != "undefined") {
|
||||
if (name && typeof value != "undefined") {
|
||||
data[name] = value;
|
||||
}
|
||||
});
|
||||
@@ -343,14 +370,14 @@ define("tinymce/ui/Container", [
|
||||
* @return {String} HTML representing the control.
|
||||
*/
|
||||
renderHtml: function() {
|
||||
var self = this, layout = self._layout;
|
||||
var self = this, layout = self._layout, role = this.settings.role;
|
||||
|
||||
self.preRender();
|
||||
layout.preRender(self);
|
||||
|
||||
return (
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '" role="' + this.settings.role + '">' +
|
||||
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">'+
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
|
||||
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
|
||||
(self.settings.html || '') + layout.renderHtml(self) +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
@@ -386,6 +413,12 @@ define("tinymce/ui/Container", [
|
||||
});
|
||||
}
|
||||
|
||||
if (!self.parent()) {
|
||||
self.keyboardNav = new KeyboardNavigation({
|
||||
root: self
|
||||
});
|
||||
}
|
||||
|
||||
return self;
|
||||
},
|
||||
|
||||
@@ -435,13 +468,13 @@ define("tinymce/ui/Container", [
|
||||
* @return {tinymce.ui.Container} Current container instance.
|
||||
*/
|
||||
reflow: function() {
|
||||
var i, items;
|
||||
var i;
|
||||
|
||||
if (this.visible()) {
|
||||
Control.repaintControls = [];
|
||||
Control.repaintControls.map = {};
|
||||
|
||||
items = this.recalc();
|
||||
this.recalc();
|
||||
i = Control.repaintControls.length;
|
||||
|
||||
while (i--) {
|
||||
|
||||
Regular → Executable
+152
-193
@@ -8,6 +8,8 @@
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/*eslint consistent-this:0 */
|
||||
|
||||
/**
|
||||
* This is the base class for all controls and containers. All UI control instances inherit
|
||||
* from this one as it has the base logic needed by all of them.
|
||||
@@ -17,22 +19,42 @@
|
||||
define("tinymce/ui/Control", [
|
||||
"tinymce/util/Class",
|
||||
"tinymce/util/Tools",
|
||||
"tinymce/util/EventDispatcher",
|
||||
"tinymce/ui/Collection",
|
||||
"tinymce/ui/DomUtils"
|
||||
], function(Class, Tools, Collection, DomUtils) {
|
||||
], function(Class, Tools, EventDispatcher, Collection, DomUtils) {
|
||||
"use strict";
|
||||
|
||||
var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" +
|
||||
" mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu", " ");
|
||||
|
||||
var elementIdCache = {};
|
||||
var hasMouseWheelEventSupport = "onmousewheel" in document;
|
||||
var hasWheelEventSupport = false;
|
||||
var classPrefix = "mce-";
|
||||
|
||||
function getEventDispatcher(obj) {
|
||||
if (!obj._eventDispatcher) {
|
||||
obj._eventDispatcher = new EventDispatcher({
|
||||
scope: obj,
|
||||
toggleEvent: function(name, state) {
|
||||
if (state && EventDispatcher.isNative(name)) {
|
||||
if (!obj._nativeEvents) {
|
||||
obj._nativeEvents = {};
|
||||
}
|
||||
|
||||
obj._nativeEvents[name] = true;
|
||||
|
||||
if (obj._rendered) {
|
||||
obj.bindPendingEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return obj._eventDispatcher;
|
||||
}
|
||||
|
||||
var Control = Class.extend({
|
||||
Statics: {
|
||||
controlIdLookup: {},
|
||||
elementIdCache: elementIdCache
|
||||
classPrefix: classPrefix
|
||||
},
|
||||
|
||||
isRtl: function() {
|
||||
@@ -45,7 +67,7 @@ define("tinymce/ui/Control", [
|
||||
* @final
|
||||
* @field {String} classPrefix
|
||||
*/
|
||||
classPrefix: "mce-",
|
||||
classPrefix: classPrefix,
|
||||
|
||||
/**
|
||||
* Constructs a new control instance with the specified settings.
|
||||
@@ -70,10 +92,11 @@ define("tinymce/ui/Control", [
|
||||
self.settings = settings = Tools.extend({}, self.Defaults, settings);
|
||||
|
||||
// Initial states
|
||||
self._id = DomUtils.id();
|
||||
self._id = settings.id || DomUtils.id();
|
||||
self._text = self._name = '';
|
||||
self._width = self._height = 0;
|
||||
self._aria = {role: settings.role};
|
||||
this._elmCache = {};
|
||||
|
||||
// Setup classes
|
||||
classes = settings.classes;
|
||||
@@ -153,10 +176,10 @@ define("tinymce/ui/Control", [
|
||||
* @return {tinymce.ui.Control} Control instance or undefined.
|
||||
*/
|
||||
getParentCtrl: function(elm) {
|
||||
var ctrl;
|
||||
var ctrl, lookup = this.getRoot().controlIdLookup;
|
||||
|
||||
while (elm) {
|
||||
ctrl = Control.controlIdLookup[elm.id];
|
||||
while (elm && lookup) {
|
||||
ctrl = lookup[elm.id];
|
||||
if (ctrl) {
|
||||
break;
|
||||
}
|
||||
@@ -182,7 +205,7 @@ define("tinymce/ui/Control", [
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(value) === "number") {
|
||||
if (typeof value === "number") {
|
||||
value = value || 0;
|
||||
|
||||
return {
|
||||
@@ -282,7 +305,7 @@ define("tinymce/ui/Control", [
|
||||
width = settings.width;
|
||||
height = settings.height;
|
||||
autoResize = settings.autoResize;
|
||||
autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
|
||||
autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height;
|
||||
|
||||
width = width || minWidth;
|
||||
height = height || minHeight;
|
||||
@@ -438,7 +461,12 @@ define("tinymce/ui/Control", [
|
||||
* @method repaint
|
||||
*/
|
||||
repaint: function() {
|
||||
var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
|
||||
var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
|
||||
|
||||
// Use Math.round on all values on IE < 9
|
||||
round = !document.createRange ? Math.round : function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
style = self.getEl().style;
|
||||
rect = self._layoutRect;
|
||||
@@ -449,35 +477,35 @@ define("tinymce/ui/Control", [
|
||||
borderH = borderBox.top + borderBox.bottom;
|
||||
|
||||
if (rect.x !== lastRepaintRect.x) {
|
||||
style.left = rect.x + 'px';
|
||||
style.left = round(rect.x) + 'px';
|
||||
lastRepaintRect.x = rect.x;
|
||||
}
|
||||
|
||||
if (rect.y !== lastRepaintRect.y) {
|
||||
style.top = rect.y + 'px';
|
||||
style.top = round(rect.y) + 'px';
|
||||
lastRepaintRect.y = rect.y;
|
||||
}
|
||||
|
||||
if (rect.w !== lastRepaintRect.w) {
|
||||
style.width = (rect.w - borderW) + 'px';
|
||||
style.width = round(rect.w - borderW) + 'px';
|
||||
lastRepaintRect.w = rect.w;
|
||||
}
|
||||
|
||||
if (rect.h !== lastRepaintRect.h) {
|
||||
style.height = (rect.h - borderH) + 'px';
|
||||
style.height = round(rect.h - borderH) + 'px';
|
||||
lastRepaintRect.h = rect.h;
|
||||
}
|
||||
|
||||
// Update body if needed
|
||||
if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
|
||||
bodyStyle = self.getEl('body').style;
|
||||
bodyStyle.width = (rect.innerW) + 'px';
|
||||
bodyStyle.width = round(rect.innerW) + 'px';
|
||||
lastRepaintRect.innerW = rect.innerW;
|
||||
}
|
||||
|
||||
if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
|
||||
bodyStyle = bodyStyle || self.getEl('body').style;
|
||||
bodyStyle.height = (rect.innerH) + 'px';
|
||||
bodyStyle.height = round(rect.innerH) + 'px';
|
||||
lastRepaintRect.innerH = rect.innerH;
|
||||
}
|
||||
|
||||
@@ -497,14 +525,18 @@ define("tinymce/ui/Control", [
|
||||
* @return {tinymce.ui.Control} Current control object.
|
||||
*/
|
||||
on: function(name, callback) {
|
||||
var self = this, bindings, handlers, names, i;
|
||||
var self = this;
|
||||
|
||||
function resolveCallbackName(name) {
|
||||
var callback, scope;
|
||||
|
||||
if (typeof name != 'string') {
|
||||
return name;
|
||||
}
|
||||
|
||||
return function(e) {
|
||||
if (!callback) {
|
||||
self.parents().each(function(ctrl) {
|
||||
self.parentsAndSelf().each(function(ctrl) {
|
||||
var callbacks = ctrl.settings.callbacks;
|
||||
|
||||
if (callbacks && (callback = callbacks[name])) {
|
||||
@@ -518,41 +550,7 @@ define("tinymce/ui/Control", [
|
||||
};
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
if (typeof(callback) == 'string') {
|
||||
callback = resolveCallbackName(callback);
|
||||
}
|
||||
|
||||
names = name.toLowerCase().split(' ');
|
||||
i = names.length;
|
||||
while (i--) {
|
||||
name = names[i];
|
||||
|
||||
bindings = self._bindings;
|
||||
if (!bindings) {
|
||||
bindings = self._bindings = {};
|
||||
}
|
||||
|
||||
handlers = bindings[name];
|
||||
if (!handlers) {
|
||||
handlers = bindings[name] = [];
|
||||
}
|
||||
|
||||
handlers.push(callback);
|
||||
|
||||
if (nativeEvents[name]) {
|
||||
if (!self._nativeEvents) {
|
||||
self._nativeEvents = {name: true};
|
||||
} else {
|
||||
self._nativeEvents[name] = true;
|
||||
}
|
||||
|
||||
if (self._rendered) {
|
||||
self.bindPendingEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getEventDispatcher(self).on(name, resolveCallbackName(callback));
|
||||
|
||||
return self;
|
||||
},
|
||||
@@ -568,46 +566,8 @@ define("tinymce/ui/Control", [
|
||||
* @return {mxex.ui.Control} Current control object.
|
||||
*/
|
||||
off: function(name, callback) {
|
||||
var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi;
|
||||
|
||||
if (bindings) {
|
||||
if (name) {
|
||||
names = name.toLowerCase().split(' ');
|
||||
i = names.length;
|
||||
while (i--) {
|
||||
name = names[i];
|
||||
handlers = bindings[name];
|
||||
|
||||
// Unbind all handlers
|
||||
if (!name) {
|
||||
for (bindingName in bindings) {
|
||||
bindings[bindingName].length = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
if (handlers) {
|
||||
// Unbind all by name
|
||||
if (!callback) {
|
||||
handlers.length = 0;
|
||||
} else {
|
||||
// Unbind specific ones
|
||||
hi = handlers.length;
|
||||
while (hi--) {
|
||||
if (handlers[hi] === callback) {
|
||||
handlers.splice(hi, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self._bindings = [];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
getEventDispatcher(this).off(name, callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -621,75 +581,22 @@ define("tinymce/ui/Control", [
|
||||
* @return {Object} Current arguments object.
|
||||
*/
|
||||
fire: function(name, args, bubble) {
|
||||
var self = this, i, l, handlers, parentCtrl;
|
||||
var self = this;
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
// Dummy function that gets replaced on the delegation state functions
|
||||
function returnFalse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dummy function that gets replaced on the delegation state functions
|
||||
function returnTrue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup empty object if args is omited
|
||||
args = args || {};
|
||||
|
||||
// Stick type into event object
|
||||
if (!args.type) {
|
||||
args.type = name;
|
||||
}
|
||||
|
||||
// Stick control into event
|
||||
if (!args.control) {
|
||||
args.control = self;
|
||||
}
|
||||
|
||||
// Add event delegation methods if they are missing
|
||||
if (!args.preventDefault) {
|
||||
// Add preventDefault method
|
||||
args.preventDefault = function() {
|
||||
args.isDefaultPrevented = returnTrue;
|
||||
};
|
||||
args = getEventDispatcher(self).fire(name, args);
|
||||
|
||||
// Add stopPropagation
|
||||
args.stopPropagation = function() {
|
||||
args.isPropagationStopped = returnTrue;
|
||||
};
|
||||
|
||||
// Add stopImmediatePropagation
|
||||
args.stopImmediatePropagation = function() {
|
||||
args.isImmediatePropagationStopped = returnTrue;
|
||||
};
|
||||
|
||||
// Add event delegation states
|
||||
args.isDefaultPrevented = returnFalse;
|
||||
args.isPropagationStopped = returnFalse;
|
||||
args.isImmediatePropagationStopped = returnFalse;
|
||||
}
|
||||
|
||||
if (self._bindings) {
|
||||
handlers = self._bindings[name];
|
||||
|
||||
if (handlers) {
|
||||
for (i = 0, l = handlers.length; i < l; i++) {
|
||||
// Execute callback and break if the callback returns a false
|
||||
if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bubble event up to parent controls
|
||||
if (bubble !== false) {
|
||||
parentCtrl = self.parent();
|
||||
while (parentCtrl && !args.isPropagationStopped()) {
|
||||
parentCtrl.fire(name, args, false);
|
||||
parentCtrl = parentCtrl.parent();
|
||||
// Bubble event up to parents
|
||||
if (bubble !== false && self.parent) {
|
||||
var parent = self.parent();
|
||||
while (parent && !args.isPropagationStopped()) {
|
||||
parent.fire(name, args, false);
|
||||
parent = parent.parent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +611,7 @@ define("tinymce/ui/Control", [
|
||||
* @return {Boolean} True/false state if the event has listeners.
|
||||
*/
|
||||
hasEventListeners: function(name) {
|
||||
return name in this._bindings;
|
||||
return getEventDispatcher(this).has(name);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -715,10 +622,10 @@ define("tinymce/ui/Control", [
|
||||
* @return {tinymce.ui.Collection} Collection with all parent controls.
|
||||
*/
|
||||
parents: function(selector) {
|
||||
var ctrl = this, parents = new Collection();
|
||||
var self = this, ctrl, parents = new Collection();
|
||||
|
||||
// Add each parent to collection
|
||||
for (ctrl = ctrl.parent(); ctrl; ctrl = ctrl.parent()) {
|
||||
for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
|
||||
parents.add(ctrl);
|
||||
}
|
||||
|
||||
@@ -730,6 +637,17 @@ define("tinymce/ui/Control", [
|
||||
return parents;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current control and it's parents.
|
||||
*
|
||||
* @method parentsAndSelf
|
||||
* @param {String} selector Optional selector expression to find parents.
|
||||
* @return {tinymce.ui.Collection} Collection with all parent controls.
|
||||
*/
|
||||
parentsAndSelf: function(selector) {
|
||||
return new Collection(this).add(this.parents(selector));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the control next to the current control.
|
||||
*
|
||||
@@ -920,15 +838,16 @@ define("tinymce/ui/Control", [
|
||||
*
|
||||
* @method getEl
|
||||
* @param {String} [suffix] Suffix to get element by.
|
||||
* @param {Boolean} [dropCache] True if the cache for the element should be dropped.
|
||||
* @return {Element} HTML DOM element for the current control or it's children.
|
||||
*/
|
||||
getEl: function(suffix, dropCache) {
|
||||
var elm, id = suffix ? this._id + '-' + suffix : this._id;
|
||||
getEl: function(suffix) {
|
||||
var id = suffix ? this._id + '-' + suffix : this._id;
|
||||
|
||||
elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id);
|
||||
if (!this._elmCache[id]) {
|
||||
this._elmCache[id] = DomUtils.get(id);
|
||||
}
|
||||
|
||||
return elm;
|
||||
return this._elmCache[id];
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -941,7 +860,7 @@ define("tinymce/ui/Control", [
|
||||
visible: function(state) {
|
||||
var self = this, parentCtrl;
|
||||
|
||||
if (typeof(state) !== "undefined") {
|
||||
if (typeof state !== "undefined") {
|
||||
if (self._visible !== state) {
|
||||
if (self._rendered) {
|
||||
self.getEl().style.display = state ? '' : 'none';
|
||||
@@ -1021,19 +940,15 @@ define("tinymce/ui/Control", [
|
||||
* @return {tinymce.ui.Control} Current control instance.
|
||||
*/
|
||||
aria: function(name, value) {
|
||||
var self = this, elm = self.getEl();
|
||||
var self = this, elm = self.getEl(self.ariaTarget);
|
||||
|
||||
if (typeof(value) === "undefined") {
|
||||
if (typeof value === "undefined") {
|
||||
return self._aria[name];
|
||||
} else {
|
||||
self._aria[name] = value;
|
||||
}
|
||||
|
||||
if (self._rendered) {
|
||||
if (name == 'label') {
|
||||
elm.setAttribute('aria-labeledby', self._id);
|
||||
}
|
||||
|
||||
elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
|
||||
}
|
||||
|
||||
@@ -1047,11 +962,11 @@ define("tinymce/ui/Control", [
|
||||
* @method encode
|
||||
* @param {String/Object/Array} text Text to entity encode.
|
||||
* @param {Boolean} [translate=true] False if the contents shouldn't be translated.
|
||||
* @return {String} Encoded and possible traslated string.
|
||||
* @return {String} Encoded and possible traslated string.
|
||||
*/
|
||||
encode: function(text, translate) {
|
||||
if (translate !== false && Control.translate) {
|
||||
text = Control.translate(text);
|
||||
if (translate !== false) {
|
||||
text = this.translate(text);
|
||||
}
|
||||
|
||||
return (text || '').replace(/[&<>"]/g, function(match) {
|
||||
@@ -1059,6 +974,17 @@ define("tinymce/ui/Control", [
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the translated string.
|
||||
*
|
||||
* @method translate
|
||||
* @param {String} text Text to translate.
|
||||
* @return {String} Translated string or the same as the input.
|
||||
*/
|
||||
translate: function(text) {
|
||||
return Control.translate ? Control.translate(text) : text;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds items before the current control.
|
||||
*
|
||||
@@ -1127,20 +1053,17 @@ define("tinymce/ui/Control", [
|
||||
DomUtils.off(elm);
|
||||
}
|
||||
|
||||
delete Control.controlIdLookup[self._id];
|
||||
delete elementIdCache[self._id];
|
||||
var lookup = self.getRoot().controlIdLookup;
|
||||
if (lookup) {
|
||||
delete lookup[self._id];
|
||||
}
|
||||
|
||||
if (elm && elm.parentNode) {
|
||||
var nodes = elm.getElementsByTagName('*');
|
||||
|
||||
i = nodes.length;
|
||||
while (i--) {
|
||||
delete elementIdCache[nodes[i].id];
|
||||
}
|
||||
|
||||
elm.parentNode.removeChild(elm);
|
||||
}
|
||||
|
||||
self._rendered = false;
|
||||
|
||||
return self;
|
||||
},
|
||||
|
||||
@@ -1230,7 +1153,12 @@ define("tinymce/ui/Control", [
|
||||
}
|
||||
|
||||
// Add instance to lookup
|
||||
Control.controlIdLookup[self._id] = self;
|
||||
var root = self.getRoot();
|
||||
if (!root.controlIdLookup) {
|
||||
root.controlIdLookup = {};
|
||||
}
|
||||
|
||||
root.controlIdLookup[self._id] = self;
|
||||
|
||||
for (var key in self._aria) {
|
||||
self.aria(key, self._aria[key]);
|
||||
@@ -1338,7 +1266,7 @@ define("tinymce/ui/Control", [
|
||||
for (i = lastParents.length - 1; i >= idx; i--) {
|
||||
lastCtrl = lastParents[i];
|
||||
lastCtrl.fire("mouseleave", {
|
||||
target : lastCtrl.getEl()
|
||||
target: lastCtrl.getEl()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1346,7 +1274,7 @@ define("tinymce/ui/Control", [
|
||||
for (i = idx; i < parents.length; i++) {
|
||||
ctrl = parents[i];
|
||||
ctrl.fire("mouseenter", {
|
||||
target : ctrl.getEl()
|
||||
target: ctrl.getEl()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1356,10 +1284,10 @@ define("tinymce/ui/Control", [
|
||||
e.preventDefault();
|
||||
|
||||
if (e.type == "mousewheel") {
|
||||
e.deltaY = - 1/40 * e.wheelDelta;
|
||||
e.deltaY = -1 / 40 * e.wheelDelta;
|
||||
|
||||
if (e.wheelDeltaX) {
|
||||
e.deltaX = -1/40 * e.wheelDeltaX;
|
||||
e.deltaX = -1 / 40 * e.wheelDeltaX;
|
||||
}
|
||||
} else {
|
||||
e.deltaX = 0;
|
||||
@@ -1391,6 +1319,11 @@ define("tinymce/ui/Control", [
|
||||
parents[i]._eventsRoot = eventRootCtrl;
|
||||
}
|
||||
|
||||
var eventRootDelegates = eventRootCtrl._delegates;
|
||||
if (!eventRootDelegates) {
|
||||
eventRootDelegates = eventRootCtrl._delegates = {};
|
||||
}
|
||||
|
||||
// Bind native event delegates
|
||||
for (name in nativeEvents) {
|
||||
if (!nativeEvents) {
|
||||
@@ -1415,9 +1348,9 @@ define("tinymce/ui/Control", [
|
||||
DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
|
||||
eventRootCtrl._hasMouseEnter = 1;
|
||||
}
|
||||
} else if (!eventRootCtrl[name]) {
|
||||
} else if (!eventRootDelegates[name]) {
|
||||
DomUtils.on(eventRootCtrl.getEl(), name, delegate);
|
||||
eventRootCtrl[name] = true;
|
||||
eventRootDelegates[name] = true;
|
||||
}
|
||||
|
||||
// Remove the event once it's bound
|
||||
@@ -1426,6 +1359,32 @@ define("tinymce/ui/Control", [
|
||||
}
|
||||
},
|
||||
|
||||
getRoot: function() {
|
||||
var ctrl = this, rootControl, parents = [];
|
||||
|
||||
while (ctrl) {
|
||||
if (ctrl.rootControl) {
|
||||
rootControl = ctrl.rootControl;
|
||||
break;
|
||||
}
|
||||
|
||||
parents.push(ctrl);
|
||||
rootControl = ctrl;
|
||||
ctrl = ctrl.parent();
|
||||
}
|
||||
|
||||
if (!rootControl) {
|
||||
rootControl = this;
|
||||
}
|
||||
|
||||
var i = parents.length;
|
||||
while (i--) {
|
||||
parents[i].rootControl = rootControl;
|
||||
}
|
||||
|
||||
return rootControl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reflows the current control and it's parents.
|
||||
* This should be used after you for example append children to the current control so
|
||||
|
||||
Regular → Executable
+10
-4
@@ -14,9 +14,11 @@ define("tinymce/ui/DomUtils", [
|
||||
], function(Tools, DOMUtils) {
|
||||
"use strict";
|
||||
|
||||
var count = 0;
|
||||
|
||||
return {
|
||||
id: function() {
|
||||
return DOMUtils.DOM.uniqueId();
|
||||
return 'mceu_' + (count++);
|
||||
},
|
||||
|
||||
createFragment: function(html) {
|
||||
@@ -55,15 +57,15 @@ define("tinymce/ui/DomUtils", [
|
||||
return document.getElementById(id);
|
||||
},
|
||||
|
||||
addClass : function(elm, cls) {
|
||||
addClass: function(elm, cls) {
|
||||
return DOMUtils.DOM.addClass(elm, cls);
|
||||
},
|
||||
|
||||
removeClass : function(elm, cls) {
|
||||
removeClass: function(elm, cls) {
|
||||
return DOMUtils.DOM.removeClass(elm, cls);
|
||||
},
|
||||
|
||||
hasClass : function(elm, cls) {
|
||||
hasClass: function(elm, cls) {
|
||||
return DOMUtils.DOM.hasClass(elm, cls);
|
||||
},
|
||||
|
||||
@@ -75,6 +77,10 @@ define("tinymce/ui/DomUtils", [
|
||||
return DOMUtils.DOM.setStyle(elm, name, value);
|
||||
},
|
||||
|
||||
getRuntimeStyle: function(elm, name) {
|
||||
return DOMUtils.DOM.getStyle(elm, name, true);
|
||||
},
|
||||
|
||||
on: function(target, name, callback, scope) {
|
||||
return DOMUtils.DOM.bind(target, name, callback, scope);
|
||||
},
|
||||
|
||||
Regular → Executable
-1
@@ -85,7 +85,6 @@ define("tinymce/ui/DragHelper", [
|
||||
height: docSize.height,
|
||||
zIndex: 0x7FFFFFFF,
|
||||
opacity: 0.0001,
|
||||
background: 'red',
|
||||
cursor: cursor
|
||||
});
|
||||
|
||||
|
||||
Regular → Executable
+26
-30
@@ -42,40 +42,36 @@ define("tinymce/ui/ElementPath", [
|
||||
return false;
|
||||
}
|
||||
|
||||
self.on('select', function(e) {
|
||||
var parents = [], node, body = editor.getBody();
|
||||
if (editor.settings.elementpath !== false) {
|
||||
self.on('select', function(e) {
|
||||
editor.focus();
|
||||
editor.selection.select(this.data()[e.index].element);
|
||||
editor.nodeChanged();
|
||||
});
|
||||
|
||||
editor.focus();
|
||||
editor.on('nodeChange', function(e) {
|
||||
var outParents = [], parents = e.parents, i = parents.length;
|
||||
|
||||
node = editor.selection.getStart();
|
||||
while (node && node != body) {
|
||||
if (!isHidden(node)) {
|
||||
parents.push(node);
|
||||
while (i--) {
|
||||
if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
|
||||
var args = editor.fire('ResolveName', {
|
||||
name: parents[i].nodeName.toLowerCase(),
|
||||
target: parents[i]
|
||||
});
|
||||
|
||||
if (!args.isDefaultPrevented()) {
|
||||
outParents.push({name: args.name, element: parents[i]});
|
||||
}
|
||||
|
||||
if (args.isPropagationStopped()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
editor.selection.select(parents[parents.length - 1 - e.index]);
|
||||
editor.nodeChanged();
|
||||
});
|
||||
|
||||
editor.on('nodeChange', function(e) {
|
||||
var parents = [], selectionParents = e.parents, i = selectionParents.length;
|
||||
|
||||
while (i--) {
|
||||
if (selectionParents[i].nodeType == 1 && !isHidden(selectionParents[i])) {
|
||||
var args = editor.fire('ResolveName', {
|
||||
name: selectionParents[i].nodeName.toLowerCase(),
|
||||
target: selectionParents[i]
|
||||
});
|
||||
|
||||
parents.push({name: args.name});
|
||||
}
|
||||
}
|
||||
|
||||
self.data(parents);
|
||||
});
|
||||
self.data(outParents);
|
||||
});
|
||||
}
|
||||
|
||||
return self._super();
|
||||
}
|
||||
|
||||
Regular → Executable
+1
-1
@@ -76,7 +76,7 @@ define("tinymce/ui/Factory", [], function() {
|
||||
}
|
||||
|
||||
// If string is specified then use it as the type
|
||||
if (typeof(type) == 'string') {
|
||||
if (typeof type == 'string') {
|
||||
settings = settings || {};
|
||||
settings.type = type;
|
||||
} else {
|
||||
|
||||
Regular → Executable
+1
-1
@@ -46,7 +46,7 @@ define("tinymce/ui/FieldSet", [
|
||||
layout.preRender(self);
|
||||
|
||||
return (
|
||||
'<fieldset id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
|
||||
'<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
|
||||
(self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
|
||||
self.settings.title + '</legend>') : '') +
|
||||
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
|
||||
|
||||
Regular → Executable
+46
-14
@@ -17,8 +17,9 @@
|
||||
* @extends tinymce.ui.ComboBox
|
||||
*/
|
||||
define("tinymce/ui/FilePicker", [
|
||||
"tinymce/ui/ComboBox"
|
||||
], function(ComboBox) {
|
||||
"tinymce/ui/ComboBox",
|
||||
"tinymce/util/Tools"
|
||||
], function(ComboBox, Tools) {
|
||||
"use strict";
|
||||
|
||||
return ComboBox.extend({
|
||||
@@ -29,22 +30,53 @@ define("tinymce/ui/FilePicker", [
|
||||
* @param {Object} settings Name/value object with settings.
|
||||
*/
|
||||
init: function(settings) {
|
||||
var self = this, editor = tinymce.activeEditor, fileBrowserCallback;
|
||||
var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings;
|
||||
var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;
|
||||
|
||||
settings.spellcheck = false;
|
||||
|
||||
fileBrowserCallback = editor.settings.file_browser_callback;
|
||||
if (fileBrowserCallback) {
|
||||
settings.icon = 'browse';
|
||||
fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
|
||||
if (fileBrowserCallbackTypes) {
|
||||
fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
|
||||
}
|
||||
|
||||
settings.onaction = function() {
|
||||
fileBrowserCallback(
|
||||
self.getEl('inp').id,
|
||||
self.getEl('inp').value,
|
||||
settings.filetype,
|
||||
window
|
||||
);
|
||||
};
|
||||
if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) {
|
||||
fileBrowserCallback = editorSettings.file_picker_callback;
|
||||
if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
|
||||
actionCallback = function() {
|
||||
var meta = self.fire('beforecall').meta;
|
||||
|
||||
meta = Tools.extend({filetype: settings.filetype}, meta);
|
||||
|
||||
// file_picker_callback(callback, currentValue, metaData)
|
||||
fileBrowserCallback.call(
|
||||
editor,
|
||||
function(value, meta) {
|
||||
self.value(value).fire('change', {meta: meta});
|
||||
},
|
||||
self.value(),
|
||||
meta
|
||||
);
|
||||
};
|
||||
} else {
|
||||
// Legacy callback: file_picker_callback(id, currentValue, filetype, window)
|
||||
fileBrowserCallback = editorSettings.file_browser_callback;
|
||||
if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
|
||||
actionCallback = function() {
|
||||
fileBrowserCallback(
|
||||
self.getEl('inp').id,
|
||||
self.value(),
|
||||
settings.filetype,
|
||||
window
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actionCallback) {
|
||||
settings.icon = 'browse';
|
||||
settings.onaction = actionCallback;
|
||||
}
|
||||
|
||||
self._super(settings);
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+5
-9
@@ -35,8 +35,8 @@ define("tinymce/ui/FlexLayout", [
|
||||
// A ton of variables, needs to be in the same scope for performance
|
||||
var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
|
||||
var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
|
||||
var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, afterName, deltaSizeName, contentSizeName;
|
||||
var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignMaxSizeName, alignBeforeName, alignAfterName;
|
||||
var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
|
||||
var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
|
||||
var alignDeltaSizeName, alignContentSizeName;
|
||||
var max = Math.max, min = Math.min;
|
||||
|
||||
@@ -63,7 +63,6 @@ define("tinymce/ui/FlexLayout", [
|
||||
maxSizeName = "maxH";
|
||||
innerSizeName = "innerH";
|
||||
beforeName = 'top';
|
||||
afterName = 'bottom';
|
||||
deltaSizeName = "deltaH";
|
||||
contentSizeName = "contentH";
|
||||
|
||||
@@ -72,7 +71,6 @@ define("tinymce/ui/FlexLayout", [
|
||||
alignAxisName = "x";
|
||||
alignInnerSizeName = "innerW";
|
||||
alignMinSizeName = "minW";
|
||||
alignMaxSizeName = "maxW";
|
||||
alignAfterName = "right";
|
||||
alignDeltaSizeName = "deltaW";
|
||||
alignContentSizeName = "contentW";
|
||||
@@ -83,7 +81,6 @@ define("tinymce/ui/FlexLayout", [
|
||||
maxSizeName = "maxW";
|
||||
innerSizeName = "innerW";
|
||||
beforeName = 'left';
|
||||
afterName = 'right';
|
||||
deltaSizeName = "deltaW";
|
||||
contentSizeName = "contentW";
|
||||
|
||||
@@ -92,7 +89,6 @@ define("tinymce/ui/FlexLayout", [
|
||||
alignAxisName = "y";
|
||||
alignInnerSizeName = "innerH";
|
||||
alignMinSizeName = "minH";
|
||||
alignMaxSizeName = "maxH";
|
||||
alignAfterName = "bottom";
|
||||
alignDeltaSizeName = "deltaH";
|
||||
alignContentSizeName = "contentH";
|
||||
@@ -171,7 +167,7 @@ define("tinymce/ui/FlexLayout", [
|
||||
ctrl = maxSizeItems[i];
|
||||
ctrlLayoutRect = ctrl.layoutRect();
|
||||
maxSize = ctrlLayoutRect[maxSizeName];
|
||||
size = ctrlLayoutRect[minSizeName] + Math.ceil(ctrlLayoutRect.flex * ratio);
|
||||
size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
|
||||
|
||||
if (size > maxSize) {
|
||||
availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
|
||||
@@ -225,12 +221,12 @@ define("tinymce/ui/FlexLayout", [
|
||||
);
|
||||
rect[alignAxisName] = contPaddingBox[alignBeforeName];
|
||||
} else if (align === "end") {
|
||||
rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
|
||||
rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
|
||||
}
|
||||
|
||||
// Calculate new size based on flex
|
||||
if (ctrlLayoutRect.flex > 0) {
|
||||
size += Math.ceil(ctrlLayoutRect.flex * ratio);
|
||||
size += ctrlLayoutRect.flex * ratio;
|
||||
}
|
||||
|
||||
rect[sizeName] = size;
|
||||
|
||||
Regular → Executable
+182
-141
@@ -25,9 +25,164 @@ define("tinymce/ui/FloatPanel", [
|
||||
], function(Panel, Movable, Resizable, DomUtils) {
|
||||
"use strict";
|
||||
|
||||
var documentClickHandler, documentScrollHandler, visiblePanels = [];
|
||||
var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
|
||||
var zOrder = [], hasModal;
|
||||
|
||||
function bindDocumentClickHandler() {
|
||||
function isChildOf(ctrl, parent) {
|
||||
while (ctrl) {
|
||||
if (ctrl == parent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ctrl = ctrl.parent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!documentClickHandler) {
|
||||
documentClickHandler = function(e) {
|
||||
// Gecko fires click event and in the wrong order on Mac so lets normalize
|
||||
if (e.button == 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide any float panel when a click is out side that float panel and the
|
||||
// float panels direct parent for example a click on a menu button
|
||||
var i = visiblePanels.length;
|
||||
while (i--) {
|
||||
var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
|
||||
|
||||
if (panel.settings.autohide) {
|
||||
if (clickCtrl) {
|
||||
if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
e = panel.fire('autohide', {target: e.target});
|
||||
if (!e.isDefaultPrevented()) {
|
||||
panel.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DomUtils.on(document, 'click', documentClickHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function bindDocumentScrollHandler() {
|
||||
if (!documentScrollHandler) {
|
||||
documentScrollHandler = function() {
|
||||
var i;
|
||||
|
||||
i = visiblePanels.length;
|
||||
while (i--) {
|
||||
repositionPanel(visiblePanels[i]);
|
||||
}
|
||||
};
|
||||
|
||||
DomUtils.on(window, 'scroll', documentScrollHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function bindWindowResizeHandler() {
|
||||
if (!windowResizeHandler) {
|
||||
var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight;
|
||||
|
||||
windowResizeHandler = function() {
|
||||
// Workaround for #7065 IE 7 fires resize events event though the window wasn't resized
|
||||
if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) {
|
||||
clientWidth = docElm.clientWidth;
|
||||
clientHeight = docElm.clientHeight;
|
||||
FloatPanel.hideAll();
|
||||
}
|
||||
};
|
||||
|
||||
DomUtils.on(window, 'resize', windowResizeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
|
||||
* also reposition all child panels of the current panel.
|
||||
*/
|
||||
function repositionPanel(panel) {
|
||||
var scrollY = DomUtils.getViewPort().y;
|
||||
|
||||
function toggleFixedChildPanels(fixed, deltaY) {
|
||||
var parent;
|
||||
|
||||
for (var i = 0; i < visiblePanels.length; i++) {
|
||||
if (visiblePanels[i] != panel) {
|
||||
parent = visiblePanels[i].parent();
|
||||
|
||||
while (parent && (parent = parent.parent())) {
|
||||
if (parent == panel) {
|
||||
visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (panel.settings.autofix) {
|
||||
if (!panel._fixed) {
|
||||
panel._autoFixY = panel.layoutRect().y;
|
||||
|
||||
if (panel._autoFixY < scrollY) {
|
||||
panel.fixed(true).layoutRect({y: 0}).repaint();
|
||||
toggleFixedChildPanels(true, scrollY - panel._autoFixY);
|
||||
}
|
||||
} else {
|
||||
if (panel._autoFixY > scrollY) {
|
||||
panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
|
||||
toggleFixedChildPanels(false, panel._autoFixY - scrollY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addRemove(add, ctrl) {
|
||||
var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
|
||||
|
||||
if (add) {
|
||||
zOrder.push(ctrl);
|
||||
} else {
|
||||
i = zOrder.length;
|
||||
|
||||
while (i--) {
|
||||
if (zOrder[i] === ctrl) {
|
||||
zOrder.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zOrder.length) {
|
||||
for (i = 0; i < zOrder.length; i++) {
|
||||
if (zOrder[i].modal) {
|
||||
zIndex++;
|
||||
topModal = zOrder[i];
|
||||
}
|
||||
|
||||
zOrder[i].getEl().style.zIndex = zIndex;
|
||||
zOrder[i].zIndex = zIndex;
|
||||
zIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
var modalBlockEl = document.getElementById(ctrl.classPrefix + 'modal-block');
|
||||
|
||||
if (topModal) {
|
||||
DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
|
||||
} else if (modalBlockEl) {
|
||||
modalBlockEl.parentNode.removeChild(modalBlockEl);
|
||||
hasModal = false;
|
||||
}
|
||||
|
||||
FloatPanel.currentZIndex = zIndex;
|
||||
}
|
||||
|
||||
var FloatPanel = Panel.extend({
|
||||
Mixins: [Movable, Resizable],
|
||||
|
||||
@@ -41,84 +196,6 @@ define("tinymce/ui/FloatPanel", [
|
||||
init: function(settings) {
|
||||
var self = this;
|
||||
|
||||
function reorder() {
|
||||
var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
|
||||
|
||||
if (zOrder.length) {
|
||||
for (i = 0; i < zOrder.length; i++) {
|
||||
if (zOrder[i].modal) {
|
||||
zIndex++;
|
||||
topModal = zOrder[i];
|
||||
}
|
||||
|
||||
zOrder[i].getEl().style.zIndex = zIndex;
|
||||
zOrder[i].zIndex = zIndex;
|
||||
zIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
var modalBlockEl = document.getElementById(self.classPrefix + 'modal-block');
|
||||
|
||||
if (topModal) {
|
||||
DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
|
||||
} else if (modalBlockEl) {
|
||||
modalBlockEl.parentNode.removeChild(modalBlockEl);
|
||||
hasModal = false;
|
||||
}
|
||||
|
||||
FloatPanel.currentZIndex = zIndex;
|
||||
}
|
||||
|
||||
function isChildOf(ctrl, parent) {
|
||||
while (ctrl) {
|
||||
if (ctrl == parent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ctrl = ctrl.parent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
|
||||
* also reposition all child panels of the current panel.
|
||||
*/
|
||||
function repositionPanel(panel) {
|
||||
var scrollY = DomUtils.getViewPort().y;
|
||||
|
||||
function toggleFixedChildPanels(fixed, deltaY) {
|
||||
var parent;
|
||||
|
||||
for (var i = 0; i < visiblePanels.length; i++) {
|
||||
if (visiblePanels[i] != panel) {
|
||||
parent = visiblePanels[i].parent();
|
||||
|
||||
while (parent && (parent = parent.parent())) {
|
||||
if (parent == panel) {
|
||||
visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (panel.settings.autofix) {
|
||||
if (!panel._fixed) {
|
||||
panel._autoFixY = panel.layoutRect().y;
|
||||
|
||||
if (panel._autoFixY < scrollY) {
|
||||
panel.fixed(true).layoutRect({y: 0}).repaint();
|
||||
toggleFixedChildPanels(true, scrollY - panel._autoFixY);
|
||||
}
|
||||
} else {
|
||||
if (panel._autoFixY > scrollY) {
|
||||
panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
|
||||
toggleFixedChildPanels(false, panel._autoFixY - scrollY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self._super(settings);
|
||||
self._eventsRoot = self;
|
||||
|
||||
@@ -126,50 +203,13 @@ define("tinymce/ui/FloatPanel", [
|
||||
|
||||
// Hide floatpanes on click out side the root button
|
||||
if (settings.autohide) {
|
||||
if (!documentClickHandler) {
|
||||
documentClickHandler = function(e) {
|
||||
var i, clickCtrl = self.getParentCtrl(e.target);
|
||||
|
||||
// Hide any float panel when a click is out side that float panel and the
|
||||
// float panels direct parent for example a click on a menu button
|
||||
i = visiblePanels.length;
|
||||
while (i--) {
|
||||
var panel = visiblePanels[i];
|
||||
|
||||
if (panel.settings.autohide) {
|
||||
if (clickCtrl) {
|
||||
if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
e = panel.fire('autohide', {target: e.target});
|
||||
if (!e.isDefaultPrevented()) {
|
||||
panel.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DomUtils.on(document, 'click', documentClickHandler);
|
||||
}
|
||||
|
||||
bindDocumentClickHandler();
|
||||
bindWindowResizeHandler();
|
||||
visiblePanels.push(self);
|
||||
}
|
||||
|
||||
if (settings.autofix) {
|
||||
if (!documentScrollHandler) {
|
||||
documentScrollHandler = function() {
|
||||
var i;
|
||||
|
||||
i = visiblePanels.length;
|
||||
while (i--) {
|
||||
repositionPanel(visiblePanels[i]);
|
||||
}
|
||||
};
|
||||
|
||||
DomUtils.on(window, 'scroll', documentScrollHandler);
|
||||
}
|
||||
bindDocumentScrollHandler();
|
||||
|
||||
self.on('move', function() {
|
||||
repositionPanel(this);
|
||||
@@ -195,22 +235,7 @@ define("tinymce/ui/FloatPanel", [
|
||||
hasModal = true;
|
||||
}
|
||||
|
||||
zOrder.push(self);
|
||||
reorder();
|
||||
}
|
||||
});
|
||||
|
||||
self.on('close hide', function(e) {
|
||||
if (e.control == self) {
|
||||
var i = zOrder.length;
|
||||
|
||||
while (i--) {
|
||||
if (zOrder[i] === self) {
|
||||
zOrder.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
reorder();
|
||||
addRemove(true, self);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -281,11 +306,14 @@ define("tinymce/ui/FloatPanel", [
|
||||
*/
|
||||
hide: function() {
|
||||
removeVisiblePanel(this);
|
||||
addRemove(false, this);
|
||||
|
||||
return this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides all visible the float panels.
|
||||
* Hide all visible float panels with he autohide setting enabled. This is for
|
||||
* manually hiding floating menus or panels.
|
||||
*
|
||||
* @method hideAll
|
||||
*/
|
||||
@@ -301,9 +329,12 @@ define("tinymce/ui/FloatPanel", [
|
||||
close: function() {
|
||||
var self = this;
|
||||
|
||||
self.fire('close');
|
||||
if (!self.fire('close').isDefaultPrevented()) {
|
||||
self.remove();
|
||||
addRemove(false, self);
|
||||
}
|
||||
|
||||
return self.remove();
|
||||
return self;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -314,11 +345,22 @@ define("tinymce/ui/FloatPanel", [
|
||||
remove: function() {
|
||||
removeVisiblePanel(this);
|
||||
this._super();
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
var self = this;
|
||||
|
||||
if (self.settings.bodyRole) {
|
||||
this.getEl('body').setAttribute('role', self.settings.bodyRole);
|
||||
}
|
||||
|
||||
return self._super();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Hides all visible the float panels.
|
||||
* Hide all visible float panels with he autohide setting enabled. This is for
|
||||
* manually hiding floating menus or panels.
|
||||
*
|
||||
* @static
|
||||
* @method hideAll
|
||||
@@ -329,8 +371,7 @@ define("tinymce/ui/FloatPanel", [
|
||||
while (i--) {
|
||||
var panel = visiblePanels[i];
|
||||
|
||||
if (panel.settings.autohide) {
|
||||
panel.fire('cancel', {}, false);
|
||||
if (panel && panel.settings.autohide) {
|
||||
panel.hide();
|
||||
visiblePanels.splice(i, 1);
|
||||
}
|
||||
|
||||
Regular → Executable
+1
-1
@@ -22,7 +22,7 @@ define("tinymce/ui/FlowLayout", [
|
||||
Defaults: {
|
||||
containerClass: 'flow-layout',
|
||||
controlClass: 'flow-layout-item',
|
||||
endClass : 'break'
|
||||
endClass: 'break'
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Regular → Executable
+31
-13
@@ -27,8 +27,9 @@
|
||||
*/
|
||||
define("tinymce/ui/Form", [
|
||||
"tinymce/ui/Container",
|
||||
"tinymce/ui/FormItem"
|
||||
], function(Container, FormItem) {
|
||||
"tinymce/ui/FormItem",
|
||||
"tinymce/util/Tools"
|
||||
], function(Container, FormItem, Tools) {
|
||||
"use strict";
|
||||
|
||||
return Container.extend({
|
||||
@@ -56,23 +57,34 @@ define("tinymce/ui/Form", [
|
||||
preRender: function() {
|
||||
var self = this, items = self.items();
|
||||
|
||||
if (!self.settings.formItemDefaults) {
|
||||
self.settings.formItemDefaults = {
|
||||
layout: 'flex',
|
||||
autoResize: "overflow",
|
||||
defaults: {flex: 1}
|
||||
};
|
||||
}
|
||||
|
||||
// Wrap any labeled items in FormItems
|
||||
items.each(function(ctrl) {
|
||||
var formItem, label = ctrl.settings.label;
|
||||
|
||||
if (label) {
|
||||
formItem = new FormItem({
|
||||
layout: 'flex',
|
||||
autoResize: "overflow",
|
||||
defaults: {flex: 1},
|
||||
items: [
|
||||
{type: 'label', text: label, flex: 0, forId: ctrl._id}
|
||||
]
|
||||
});
|
||||
formItem = new FormItem(Tools.extend({
|
||||
items: {
|
||||
type: 'label',
|
||||
id: ctrl._id + '-l',
|
||||
text: label,
|
||||
flex: 0,
|
||||
forId: ctrl._id,
|
||||
disabled: ctrl.disabled()
|
||||
}
|
||||
}, self.settings.formItemDefaults));
|
||||
|
||||
formItem.type = 'formitem';
|
||||
ctrl.aria('labelledby', ctrl._id + '-l');
|
||||
|
||||
if (typeof(ctrl.settings.flex) == "undefined") {
|
||||
if (typeof ctrl.settings.flex == "undefined") {
|
||||
ctrl.settings.flex = 1;
|
||||
}
|
||||
|
||||
@@ -88,13 +100,19 @@ define("tinymce/ui/Form", [
|
||||
* @private
|
||||
*/
|
||||
recalcLabels: function() {
|
||||
var self = this, maxLabelWidth = 0, labels = [], i, labelGap;
|
||||
var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items;
|
||||
|
||||
if (self.settings.labelGapCalc === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.items().filter('formitem').each(function(item) {
|
||||
if (self.settings.labelGapCalc == "children") {
|
||||
items = self.find('formitem');
|
||||
} else {
|
||||
items = self.items();
|
||||
}
|
||||
|
||||
items.filter('formitem').each(function(item) {
|
||||
var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
|
||||
|
||||
maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
|
||||
|
||||
Regular → Executable
+1
-1
@@ -43,7 +43,7 @@ define("tinymce/ui/FormItem", [
|
||||
layout.preRender(self);
|
||||
|
||||
return (
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
|
||||
(self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
|
||||
self.settings.title + '</div>') : '') +
|
||||
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
|
||||
|
||||
Regular → Executable
+99
-200
@@ -41,127 +41,6 @@ define("tinymce/ui/FormatControls", [
|
||||
function registerControls(editor) {
|
||||
var formatMenu;
|
||||
|
||||
// Generates a preview for a format
|
||||
function getPreviewCss(format) {
|
||||
var name, previewElm, dom = editor.dom;
|
||||
var previewCss = '', parentFontSize, previewStyles;
|
||||
|
||||
previewStyles = editor.settings.preview_styles;
|
||||
|
||||
// No preview forced
|
||||
if (previewStyles === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Default preview
|
||||
if (!previewStyles) {
|
||||
previewStyles = 'font-family font-size font-weight text-decoration ' +
|
||||
'text-transform color background-color border border-radius';
|
||||
}
|
||||
|
||||
// Removes any variables since these can't be previewed
|
||||
function removeVars(val) {
|
||||
return val.replace(/%(\w+)/g, '');
|
||||
}
|
||||
|
||||
// Create block/inline element to use for preview
|
||||
format = editor.formatter.get(format);
|
||||
if (!format) {
|
||||
return;
|
||||
}
|
||||
|
||||
format = format[0];
|
||||
name = format.block || format.inline || 'span';
|
||||
previewElm = dom.create(name);
|
||||
|
||||
// Add format styles to preview element
|
||||
each(format.styles, function(value, name) {
|
||||
value = removeVars(value);
|
||||
|
||||
if (value) {
|
||||
dom.setStyle(previewElm, name, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Add attributes to preview element
|
||||
each(format.attributes, function(value, name) {
|
||||
value = removeVars(value);
|
||||
|
||||
if (value) {
|
||||
dom.setAttrib(previewElm, name, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Add classes to preview element
|
||||
each(format.classes, function(value) {
|
||||
value = removeVars(value);
|
||||
|
||||
if (!dom.hasClass(previewElm, value)) {
|
||||
dom.addClass(previewElm, value);
|
||||
}
|
||||
});
|
||||
|
||||
editor.fire('PreviewFormats');
|
||||
|
||||
// Add the previewElm outside the visual area
|
||||
dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
|
||||
editor.getBody().appendChild(previewElm);
|
||||
|
||||
// Get parent container font size so we can compute px values out of em/% for older IE:s
|
||||
parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
|
||||
parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
|
||||
|
||||
each(previewStyles.split(' '), function(name) {
|
||||
var value = dom.getStyle(previewElm, name, true);
|
||||
|
||||
// If background is transparent then check if the body has a background color we can use
|
||||
if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
|
||||
value = dom.getStyle(editor.getBody(), name, true);
|
||||
|
||||
// Ignore white since it's the default color, not the nicest fix
|
||||
// TODO: Fix this by detecting runtime style
|
||||
if (dom.toHex(value).toLowerCase() == '#ffffff') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (name == 'color') {
|
||||
// Ignore black since it's the default color, not the nicest fix
|
||||
// TODO: Fix this by detecting runtime style
|
||||
if (dom.toHex(value).toLowerCase() == '#000000') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Old IE won't calculate the font size so we need to do that manually
|
||||
if (name == 'font-size') {
|
||||
if (/em|%$/.test(value)) {
|
||||
if (parentFontSize === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert font size from em/% to px
|
||||
value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
|
||||
value = (value * parentFontSize) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
if (name == "border" && value) {
|
||||
previewCss += 'padding:0 2px;';
|
||||
}
|
||||
|
||||
previewCss += name + ':' + value + ';';
|
||||
});
|
||||
|
||||
editor.fire('AfterPreviewFormats');
|
||||
|
||||
//previewCss += 'line-height:normal';
|
||||
|
||||
dom.remove(previewElm);
|
||||
|
||||
return previewCss;
|
||||
}
|
||||
|
||||
function createListBoxChangeHandler(items, formatName) {
|
||||
return function() {
|
||||
var self = this;
|
||||
@@ -198,7 +77,7 @@ define("tinymce/ui/FormatControls", [
|
||||
}
|
||||
|
||||
function createFormats(formats) {
|
||||
formats = formats.split(';');
|
||||
formats = formats.replace(/;$/, '').split(';');
|
||||
|
||||
var i = formats.length;
|
||||
while (i--) {
|
||||
@@ -212,13 +91,13 @@ define("tinymce/ui/FormatControls", [
|
||||
var count = 0, newFormats = [];
|
||||
|
||||
var defaultStyleFormats = [
|
||||
{title: 'Headers', items: [
|
||||
{title: 'Header 1', format: 'h1'},
|
||||
{title: 'Header 2', format: 'h2'},
|
||||
{title: 'Header 3', format: 'h3'},
|
||||
{title: 'Header 4', format: 'h4'},
|
||||
{title: 'Header 5', format: 'h5'},
|
||||
{title: 'Header 6', format: 'h6'}
|
||||
{title: 'Headings', items: [
|
||||
{title: 'Heading 1', format: 'h1'},
|
||||
{title: 'Heading 2', format: 'h2'},
|
||||
{title: 'Heading 3', format: 'h3'},
|
||||
{title: 'Heading 4', format: 'h4'},
|
||||
{title: 'Heading 5', format: 'h5'},
|
||||
{title: 'Heading 6', format: 'h6'}
|
||||
]},
|
||||
|
||||
{title: 'Inline', items: [
|
||||
@@ -270,6 +149,7 @@ define("tinymce/ui/FormatControls", [
|
||||
}
|
||||
|
||||
menuItem.format = formatName;
|
||||
menuItem.cmd = format.cmd;
|
||||
}
|
||||
|
||||
menu.push(menuItem);
|
||||
@@ -278,17 +158,31 @@ define("tinymce/ui/FormatControls", [
|
||||
return menu;
|
||||
}
|
||||
|
||||
function createStylesMenu() {
|
||||
var menu;
|
||||
|
||||
if (editor.settings.style_formats_merge) {
|
||||
if (editor.settings.style_formats) {
|
||||
menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
|
||||
} else {
|
||||
menu = createMenu(defaultStyleFormats);
|
||||
}
|
||||
} else {
|
||||
menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
editor.on('init', function() {
|
||||
each(newFormats, function(format) {
|
||||
editor.formatter.register(format.name, format);
|
||||
});
|
||||
});
|
||||
|
||||
var menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
|
||||
|
||||
menu = {
|
||||
return {
|
||||
type: 'menu',
|
||||
items: menu,
|
||||
items: createStylesMenu(),
|
||||
onPostRender: function(e) {
|
||||
editor.fire('renderFormatsMenu', {control: e.control});
|
||||
},
|
||||
@@ -297,30 +191,40 @@ define("tinymce/ui/FormatControls", [
|
||||
|
||||
textStyle: function() {
|
||||
if (this.settings.format) {
|
||||
return getPreviewCss(this.settings.format);
|
||||
return editor.formatter.getCssText(this.settings.format);
|
||||
}
|
||||
},
|
||||
|
||||
onPostRender: function() {
|
||||
var self = this, formatName = this.settings.format;
|
||||
var self = this;
|
||||
|
||||
if (formatName) {
|
||||
self.parent().on('show', function() {
|
||||
self.parent().on('show', function() {
|
||||
var formatName, command;
|
||||
|
||||
formatName = self.settings.format;
|
||||
if (formatName) {
|
||||
self.disabled(!editor.formatter.canApply(formatName));
|
||||
self.active(editor.formatter.match(formatName));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
command = self.settings.cmd;
|
||||
if (command) {
|
||||
self.active(editor.queryCommandState(command));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onclick: function() {
|
||||
if (this.settings.format) {
|
||||
toggleFormat(this.settings.format);
|
||||
}
|
||||
|
||||
if (this.settings.cmd) {
|
||||
editor.execCommand(this.settings.cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
formatMenu = createFormatMenu();
|
||||
@@ -367,7 +271,6 @@ define("tinymce/ui/FormatControls", [
|
||||
paste: ['Paste', 'Paste'],
|
||||
help: ['Help', 'mceHelp'],
|
||||
selectall: ['Select all', 'SelectAll'],
|
||||
hr: ['Insert horizontal rule', 'InsertHorizontalRule'],
|
||||
removeformat: ['Clear formatting', 'RemoveFormat'],
|
||||
visualaid: ['Visual aids', 'mceToggleVisualAid'],
|
||||
newdocument: ['New document', 'mceNewDocument']
|
||||
@@ -380,7 +283,7 @@ define("tinymce/ui/FormatControls", [
|
||||
|
||||
// Simple command controls with format state
|
||||
each({
|
||||
blockquote: ['Toggle blockquote', 'mceBlockQuote'],
|
||||
blockquote: ['Blockquote', 'mceBlockQuote'],
|
||||
numlist: ['Numbered list', 'InsertOrderedList'],
|
||||
bullist: ['Bullet list', 'InsertUnorderedList'],
|
||||
subscript: ['Subscript', 'Subscript'],
|
||||
@@ -412,30 +315,21 @@ define("tinymce/ui/FormatControls", [
|
||||
});
|
||||
});
|
||||
|
||||
function hasUndo() {
|
||||
return editor.undoManager ? editor.undoManager.hasUndo() : false;
|
||||
}
|
||||
function toggleUndoRedoState(type) {
|
||||
return function() {
|
||||
var self = this;
|
||||
|
||||
function hasRedo() {
|
||||
return editor.undoManager ? editor.undoManager.hasRedo() : false;
|
||||
}
|
||||
type = type == 'redo' ? 'hasRedo' : 'hasUndo';
|
||||
|
||||
function toggleUndoState() {
|
||||
var self = this;
|
||||
function checkState() {
|
||||
return editor.undoManager ? editor.undoManager[type]() : false;
|
||||
}
|
||||
|
||||
self.disabled(!hasUndo());
|
||||
editor.on('Undo Redo AddUndo TypingUndo', function() {
|
||||
self.disabled(!hasUndo());
|
||||
});
|
||||
}
|
||||
|
||||
function toggleRedoState() {
|
||||
var self = this;
|
||||
|
||||
self.disabled(!hasRedo());
|
||||
editor.on('Undo Redo AddUndo TypingUndo', function() {
|
||||
self.disabled(!hasRedo());
|
||||
});
|
||||
self.disabled(!checkState());
|
||||
editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() {
|
||||
self.disabled(!checkState());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function toggleVisualAidState() {
|
||||
@@ -450,19 +344,18 @@ define("tinymce/ui/FormatControls", [
|
||||
|
||||
editor.addButton('undo', {
|
||||
tooltip: 'Undo',
|
||||
onPostRender: toggleUndoState,
|
||||
onPostRender: toggleUndoRedoState('undo'),
|
||||
cmd: 'undo'
|
||||
});
|
||||
|
||||
editor.addButton('redo', {
|
||||
tooltip: 'Redo',
|
||||
onPostRender: toggleRedoState,
|
||||
onPostRender: toggleUndoRedoState('redo'),
|
||||
cmd: 'redo'
|
||||
});
|
||||
|
||||
editor.addMenuItem('newdocument', {
|
||||
text: 'New document',
|
||||
shortcut: 'Ctrl+N',
|
||||
icon: 'newdocument',
|
||||
cmd: 'mceNewDocument'
|
||||
});
|
||||
@@ -470,16 +363,16 @@ define("tinymce/ui/FormatControls", [
|
||||
editor.addMenuItem('undo', {
|
||||
text: 'Undo',
|
||||
icon: 'undo',
|
||||
shortcut: 'Ctrl+Z',
|
||||
onPostRender: toggleUndoState,
|
||||
shortcut: 'Meta+Z',
|
||||
onPostRender: toggleUndoRedoState('undo'),
|
||||
cmd: 'undo'
|
||||
});
|
||||
|
||||
editor.addMenuItem('redo', {
|
||||
text: 'Redo',
|
||||
icon: 'redo',
|
||||
shortcut: 'Ctrl+Y',
|
||||
onPostRender: toggleRedoState,
|
||||
shortcut: 'Meta+Y',
|
||||
onPostRender: toggleUndoRedoState('redo'),
|
||||
cmd: 'redo'
|
||||
});
|
||||
|
||||
@@ -491,12 +384,12 @@ define("tinymce/ui/FormatControls", [
|
||||
});
|
||||
|
||||
each({
|
||||
cut: ['Cut', 'Cut', 'Ctrl+X'],
|
||||
copy: ['Copy', 'Copy', 'Ctrl+C'],
|
||||
paste: ['Paste', 'Paste', 'Ctrl+V'],
|
||||
selectall: ['Select all', 'SelectAll', 'Ctrl+A'],
|
||||
bold: ['Bold', 'Bold', 'Ctrl+B'],
|
||||
italic: ['Italic', 'Italic', 'Ctrl+I'],
|
||||
cut: ['Cut', 'Cut', 'Meta+X'],
|
||||
copy: ['Copy', 'Copy', 'Meta+C'],
|
||||
paste: ['Paste', 'Paste', 'Meta+V'],
|
||||
selectall: ['Select all', 'SelectAll', 'Meta+A'],
|
||||
bold: ['Bold', 'Bold', 'Meta+B'],
|
||||
italic: ['Italic', 'Italic', 'Meta+I'],
|
||||
underline: ['Underline', 'Underline'],
|
||||
strikethrough: ['Strikethrough', 'Strikethrough'],
|
||||
subscript: ['Subscript', 'Subscript'],
|
||||
@@ -534,14 +427,13 @@ define("tinymce/ui/FormatControls", [
|
||||
editor.addButton('formatselect', function() {
|
||||
var items = [], blocks = createFormats(editor.settings.block_formats ||
|
||||
'Paragraph=p;' +
|
||||
'Address=address;' +
|
||||
'Pre=pre;' +
|
||||
'Header 1=h1;' +
|
||||
'Header 2=h2;' +
|
||||
'Header 3=h3;' +
|
||||
'Header 4=h4;' +
|
||||
'Header 5=h5;' +
|
||||
'Header 6=h6'
|
||||
'Heading 1=h1;' +
|
||||
'Heading 2=h2;' +
|
||||
'Heading 3=h3;' +
|
||||
'Heading 4=h4;' +
|
||||
'Heading 5=h5;' +
|
||||
'Heading 6=h6;' +
|
||||
'Preformatted=pre'
|
||||
);
|
||||
|
||||
each(blocks, function(block) {
|
||||
@@ -549,14 +441,14 @@ define("tinymce/ui/FormatControls", [
|
||||
text: block[0],
|
||||
value: block[1],
|
||||
textStyle: function() {
|
||||
return getPreviewCss(block[1]);
|
||||
return editor.formatter.getCssText(block[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'listbox',
|
||||
text: {raw: blocks[0][0]},
|
||||
text: blocks[0][0],
|
||||
values: items,
|
||||
fixedWidth: true,
|
||||
onselect: toggleFormat,
|
||||
@@ -566,21 +458,21 @@ define("tinymce/ui/FormatControls", [
|
||||
|
||||
editor.addButton('fontselect', function() {
|
||||
var defaultFontsFormats =
|
||||
'Andale Mono=andale mono,times;' +
|
||||
'Andale Mono=andale mono,monospace;' +
|
||||
'Arial=arial,helvetica,sans-serif;' +
|
||||
'Arial Black=arial black,avant garde;' +
|
||||
'Book Antiqua=book antiqua,palatino;' +
|
||||
'Arial Black=arial black,sans-serif;' +
|
||||
'Book Antiqua=book antiqua,palatino,serif;' +
|
||||
'Comic Sans MS=comic sans ms,sans-serif;' +
|
||||
'Courier New=courier new,courier;' +
|
||||
'Georgia=georgia,palatino;' +
|
||||
'Helvetica=helvetica;' +
|
||||
'Impact=impact,chicago;' +
|
||||
'Courier New=courier new,courier,monospace;' +
|
||||
'Georgia=georgia,palatino,serif;' +
|
||||
'Helvetica=helvetica,arial,sans-serif;' +
|
||||
'Impact=impact,sans-serif;' +
|
||||
'Symbol=symbol;' +
|
||||
'Tahoma=tahoma,arial,helvetica,sans-serif;' +
|
||||
'Terminal=terminal,monaco;' +
|
||||
'Times New Roman=times new roman,times;' +
|
||||
'Trebuchet MS=trebuchet ms,geneva;' +
|
||||
'Verdana=verdana,geneva;' +
|
||||
'Terminal=terminal,monaco,monospace;' +
|
||||
'Times New Roman=times new roman,times,serif;' +
|
||||
'Trebuchet MS=trebuchet ms,geneva,sans-serif;' +
|
||||
'Verdana=verdana,geneva,sans-serif;' +
|
||||
'Webdings=webdings;' +
|
||||
'Wingdings=wingdings,zapf dingbats';
|
||||
|
||||
@@ -614,7 +506,14 @@ define("tinymce/ui/FormatControls", [
|
||||
var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
|
||||
|
||||
each(fontsize_formats.split(' '), function(item) {
|
||||
items.push({text: item, value: item});
|
||||
var text = item, value = item;
|
||||
// Allow text=value font sizes.
|
||||
var values = item.split('=');
|
||||
if (values.length > 1) {
|
||||
text = values[0];
|
||||
value = values[1];
|
||||
}
|
||||
items.push({text: text, value: value});
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -637,4 +536,4 @@ define("tinymce/ui/FormatControls", [
|
||||
menu: formatMenu
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Regular → Executable
+13
-7
@@ -37,7 +37,7 @@ define("tinymce/ui/GridLayout", [
|
||||
recalc: function(container) {
|
||||
var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
|
||||
ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
|
||||
colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, alignX, alignY, availableWidth, availableHeight;
|
||||
colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx;
|
||||
|
||||
// Get layout settings
|
||||
settings = container.settings;
|
||||
@@ -50,12 +50,13 @@ define("tinymce/ui/GridLayout", [
|
||||
alignH = settings.alignH || settings.align;
|
||||
alignV = settings.alignV || settings.align;
|
||||
contPaddingBox = container._paddingBox;
|
||||
reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl();
|
||||
|
||||
if (alignH && typeof(alignH) == "string") {
|
||||
if (alignH && typeof alignH == "string") {
|
||||
alignH = [alignH];
|
||||
}
|
||||
|
||||
if (alignV && typeof(alignV) == "string") {
|
||||
if (alignV && typeof alignV == "string") {
|
||||
alignV = [alignV];
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ define("tinymce/ui/GridLayout", [
|
||||
// Calculate new column widths based on flex values
|
||||
var ratio = availableWidth / totalFlex;
|
||||
for (x = 0; x < cols; x++) {
|
||||
colWidths[x] += flexWidths ? Math.ceil(flexWidths[x] * ratio) : ratio;
|
||||
colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
|
||||
}
|
||||
|
||||
// Move/resize controls
|
||||
@@ -176,7 +177,13 @@ define("tinymce/ui/GridLayout", [
|
||||
height = rowHeights[y] + flexV;
|
||||
|
||||
for (x = 0; x < cols; x++) {
|
||||
ctrl = items[y * cols + x];
|
||||
if (reverseRows) {
|
||||
idx = y * cols + cols - 1 - x;
|
||||
} else {
|
||||
idx = y * cols + x;
|
||||
}
|
||||
|
||||
ctrl = items[idx];
|
||||
|
||||
// No more controls to render then break
|
||||
if (!ctrl) {
|
||||
@@ -187,7 +194,6 @@ define("tinymce/ui/GridLayout", [
|
||||
ctrlSettings = ctrl.settings;
|
||||
ctrlLayoutRect = ctrl.layoutRect();
|
||||
width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
|
||||
alignX = alignY = 0;
|
||||
ctrlLayoutRect.x = posX;
|
||||
ctrlLayoutRect.y = posY;
|
||||
|
||||
@@ -205,7 +211,7 @@ define("tinymce/ui/GridLayout", [
|
||||
align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
|
||||
if (align == "center") {
|
||||
ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
|
||||
} else if (align == "bottom") {
|
||||
} else if (align == "bottom") {
|
||||
ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
|
||||
} else if (align == "stretch") {
|
||||
ctrlLayoutRect.h = height;
|
||||
|
||||
Regular → Executable
+1
@@ -37,6 +37,7 @@ define("tinymce/ui/Iframe", [
|
||||
self.addClass('iframe');
|
||||
self.canFocus = false;
|
||||
|
||||
/*eslint no-script-url:0 */
|
||||
return (
|
||||
'<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
|
||||
(self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
|
||||
|
||||
Regular → Executable
+271
-211
@@ -14,89 +14,51 @@
|
||||
* @class tinymce.ui.KeyboardNavigation
|
||||
*/
|
||||
define("tinymce/ui/KeyboardNavigation", [
|
||||
"tinymce/ui/DomUtils"
|
||||
], function(DomUtils) {
|
||||
], function() {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Create a new KeyboardNavigation instance to handle the focus for a specific element.
|
||||
* This class handles all keyboard navigation for WAI-ARIA support. Each root container
|
||||
* gets an instance of this class.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} settings the settings object to define how keyboard navigation works.
|
||||
*
|
||||
* @setting {tinymce.ui.Control} root the root control navigation focus movement is scoped to this root.
|
||||
* @setting {Array} items an array containing the items to move focus between. Every object in this array must have an
|
||||
* id attribute which maps to the actual DOM element and it must be able to have focus i.e. tabIndex=-1.
|
||||
* @setting {Function} onCancel the callback for when the user presses escape or otherwise indicates canceling.
|
||||
* @setting {Function} onAction (optional) the action handler to call when the user activates an item.
|
||||
* @setting {Boolean} enableLeftRight (optional, default) when true, the up/down arrows move through items.
|
||||
* @setting {Boolean} enableUpDown (optional) when true, the up/down arrows move through items.
|
||||
* Note for both up/down and left/right explicitly set both enableLeftRight and enableUpDown to true.
|
||||
*/
|
||||
return function(settings) {
|
||||
var root = settings.root, enableUpDown = settings.enableUpDown !== false;
|
||||
var enableLeftRight = settings.enableLeftRight !== false;
|
||||
var items = settings.items, focussedId;
|
||||
var root = settings.root, focusedElement, focusedControl;
|
||||
|
||||
/**
|
||||
* Initializes the items array if needed. This will collect items/elements
|
||||
* from the specified root control.
|
||||
*/
|
||||
function initItems() {
|
||||
if (!items) {
|
||||
items = [];
|
||||
|
||||
if (root.find) {
|
||||
// Root is a container then get child elements using the UI API
|
||||
root.find('*').each(function(ctrl) {
|
||||
if (ctrl.canFocus) {
|
||||
items.push(ctrl.getEl());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Root is a control/widget then get the child elements of that control
|
||||
var elements = root.getEl().getElementsByTagName('*');
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (elements[i].id && elements[i]) {
|
||||
items.push(elements[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
focusedElement = document.activeElement;
|
||||
} catch (ex) {
|
||||
// IE sometimes fails to return a proper element
|
||||
focusedElement = document.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently focused element.
|
||||
*
|
||||
* @private
|
||||
* @return {Element} Currently focused element.
|
||||
*/
|
||||
function getFocusElement() {
|
||||
return document.getElementById(focussedId);
|
||||
}
|
||||
focusedControl = root.getParentCtrl(focusedElement);
|
||||
|
||||
/**
|
||||
* Returns the currently focused elements wai aria role.
|
||||
* Returns the currently focused elements wai aria role of the currently
|
||||
* focused element or specified element.
|
||||
*
|
||||
* @private
|
||||
* @param {Element} elm Optional element to get role from.
|
||||
* @return {String} Role of specified element.
|
||||
*/
|
||||
function getRole(elm) {
|
||||
elm = elm || getFocusElement();
|
||||
elm = elm || focusedElement;
|
||||
|
||||
return elm && elm.getAttribute('role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the role of the parent element.
|
||||
* Returns the wai role of the parent element of the currently
|
||||
* focused element or specified element.
|
||||
*
|
||||
* @private
|
||||
* @param {Element} elm Optional element to get parent role from.
|
||||
* @return {String} Role of the first parent that has a role.
|
||||
*/
|
||||
function getParentRole(elm) {
|
||||
var role, parent = elm || getFocusElement();
|
||||
var role, parent = elm || focusedElement;
|
||||
|
||||
while ((parent = parent.parentNode)) {
|
||||
if ((role = getRole(parent))) {
|
||||
@@ -106,14 +68,14 @@ define("tinymce/ui/KeyboardNavigation", [
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an wai aria property by name.
|
||||
* Returns a wai aria property by name for example aria-selected.
|
||||
*
|
||||
* @private
|
||||
* @param {String} name Name of the aria property to get for example "disabled".
|
||||
* @return {String} Aria property value.
|
||||
*/
|
||||
function getAriaProp(name) {
|
||||
var elm = document.getElementById(focussedId);
|
||||
var elm = focusedElement;
|
||||
|
||||
if (elm) {
|
||||
return elm.getAttribute('aria-' + name);
|
||||
@@ -121,218 +83,316 @@ define("tinymce/ui/KeyboardNavigation", [
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the onAction event callback. This is when the user presses enter/space.
|
||||
* Is the element a text input element or not.
|
||||
*
|
||||
* @private
|
||||
* @param {Element} elm Element to check if it's an text input element or not.
|
||||
* @return {Boolean} True/false if the element is a text element or not.
|
||||
*/
|
||||
function action() {
|
||||
var focusElm = getFocusElement();
|
||||
function isTextInputElement(elm) {
|
||||
var tagName = elm.tagName.toUpperCase();
|
||||
|
||||
if (focusElm && (focusElm.nodeName == "TEXTAREA" || focusElm.type == "text")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.onAction) {
|
||||
settings.onAction(focussedId);
|
||||
} else {
|
||||
DomUtils.fire(getFocusElement(), 'click', {keyboard: true});
|
||||
}
|
||||
|
||||
return true;
|
||||
// Notice: since type can be "email" etc we don't check the type
|
||||
// So all input elements gets treated as text input elements
|
||||
return tagName == "INPUT" || tagName == "TEXTAREA";
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the current navigation. The same as pressing the Esc key.
|
||||
* Returns true/false if the specified element can be focused or not.
|
||||
*
|
||||
* @method cancel
|
||||
* @private
|
||||
* @param {Element} elm DOM element to check if it can be focused or not.
|
||||
* @return {Boolean} True/false if the element can have focus.
|
||||
*/
|
||||
function cancel() {
|
||||
var focusElm;
|
||||
|
||||
if (settings.onCancel) {
|
||||
if ((focusElm = getFocusElement())) {
|
||||
focusElm.blur();
|
||||
}
|
||||
|
||||
settings.onCancel();
|
||||
} else {
|
||||
settings.root.fire('cancel');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the focus to the next or previous item. It will wrap to start/end if it can't move.
|
||||
*
|
||||
* @method moveFocus
|
||||
* @param {Number} dir Direction for move -1 or 1.
|
||||
*/
|
||||
function moveFocus(dir) {
|
||||
var idx = -1, focusElm, i;
|
||||
var visibleItems = [];
|
||||
|
||||
function isVisible(elm) {
|
||||
var rootElm = root ? root.getEl() : document.body;
|
||||
|
||||
while (elm && elm != rootElm) {
|
||||
if (elm.style.display == 'none') {
|
||||
return false;
|
||||
}
|
||||
|
||||
elm = elm.parentNode;
|
||||
}
|
||||
|
||||
function canFocus(elm) {
|
||||
if (isTextInputElement(elm) && !elm.hidden) {
|
||||
return true;
|
||||
}
|
||||
|
||||
initItems();
|
||||
if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Optimize this, will be slow on lots of items
|
||||
i = visibleItems.length;
|
||||
for (i = 0; i < items.length; i++) {
|
||||
if (isVisible(items[i])) {
|
||||
visibleItems.push(items[i]);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of focusable visible elements within the specified container element.
|
||||
*
|
||||
* @private
|
||||
* @param {Element} elm DOM element to find focusable elements within.
|
||||
* @return {Array} Array of focusable elements.
|
||||
*/
|
||||
function getFocusElements(elm) {
|
||||
var elements = [];
|
||||
|
||||
function collect(elm) {
|
||||
if (elm.nodeType != 1 || elm.style.display == 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canFocus(elm)) {
|
||||
elements.push(elm);
|
||||
}
|
||||
|
||||
for (var i = 0; i < elm.childNodes.length; i++) {
|
||||
collect(elm.childNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
i = visibleItems.length;
|
||||
while (i--) {
|
||||
if (visibleItems[i].id === focussedId) {
|
||||
idx = i;
|
||||
collect(elm || root.getEl());
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the navigation root control for the specified control. The navigation root
|
||||
* is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
|
||||
* It will look for parents of the specified target control or the currently focused control if this option is omitted.
|
||||
*
|
||||
* @private
|
||||
* @param {tinymce.ui.Control} targetControl Optional target control to find root of.
|
||||
* @return {tinymce.ui.Control} Navigation root control.
|
||||
*/
|
||||
function getNavigationRoot(targetControl) {
|
||||
var navigationRoot, controls;
|
||||
|
||||
targetControl = targetControl || focusedControl;
|
||||
controls = targetControl.parents().toArray();
|
||||
controls.unshift(targetControl);
|
||||
|
||||
for (var i = 0; i < controls.length; i++) {
|
||||
navigationRoot = controls[i];
|
||||
|
||||
if (navigationRoot.settings.ariaRoot) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return navigationRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the first item in the specified targetControl element or the last aria index if the
|
||||
* navigation root has the ariaRemember option enabled.
|
||||
*
|
||||
* @private
|
||||
* @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
|
||||
*/
|
||||
function focusFirst(targetControl) {
|
||||
var navigationRoot = getNavigationRoot(targetControl);
|
||||
var focusElements = getFocusElements(navigationRoot.getEl());
|
||||
|
||||
if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
|
||||
moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
|
||||
} else {
|
||||
moveFocusToIndex(0, focusElements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the focus to the specified index within the elements list.
|
||||
* This will scope the index to the size of the element list if it changed.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} idx Specified index to move to.
|
||||
* @param {Array} elements Array with dom elements to move focus within.
|
||||
* @return {Number} Input index or a changed index if it was out of range.
|
||||
*/
|
||||
function moveFocusToIndex(idx, elements) {
|
||||
if (idx < 0) {
|
||||
idx = elements.length - 1;
|
||||
} else if (idx >= elements.length) {
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
if (elements[idx]) {
|
||||
elements[idx].focus();
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the focus forwards or backwards.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} dir Direction to move in positive means forward, negative means backwards.
|
||||
* @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
|
||||
*/
|
||||
function moveFocus(dir, elements) {
|
||||
var idx = -1, navigationRoot = getNavigationRoot();
|
||||
|
||||
elements = elements || getFocusElements(navigationRoot.getEl());
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (elements[i] === focusedElement) {
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
idx += dir;
|
||||
if (idx < 0) {
|
||||
idx = visibleItems.length - 1;
|
||||
} else if (idx >= visibleItems.length) {
|
||||
idx = 0;
|
||||
}
|
||||
navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
|
||||
}
|
||||
|
||||
focusElm = visibleItems[idx];
|
||||
focusElm.focus();
|
||||
focussedId = focusElm.id;
|
||||
/**
|
||||
* Moves the focus to the left this is called by the left key.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function left() {
|
||||
var parentRole = getParentRole();
|
||||
|
||||
if (settings.actOnFocus) {
|
||||
action();
|
||||
if (parentRole == "tablist") {
|
||||
moveFocus(-1, getFocusElements(focusedElement.parentNode));
|
||||
} else if (focusedControl.parent().submenu) {
|
||||
cancel();
|
||||
} else {
|
||||
moveFocus(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves focus to the first item or the last focused item if root is a toolbar.
|
||||
* Moves the focus to the right this is called by the right key.
|
||||
*
|
||||
* @method focusFirst
|
||||
* @return {[type]} [description]
|
||||
* @private
|
||||
*/
|
||||
function focusFirst() {
|
||||
var i, rootRole;
|
||||
function right() {
|
||||
var role = getRole(), parentRole = getParentRole();
|
||||
|
||||
rootRole = getRole(settings.root.getEl());
|
||||
initItems();
|
||||
if (parentRole == "tablist") {
|
||||
moveFocus(1, getFocusElements(focusedElement.parentNode));
|
||||
} else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
|
||||
enter();
|
||||
} else {
|
||||
moveFocus(1);
|
||||
}
|
||||
}
|
||||
|
||||
i = items.length;
|
||||
while (i--) {
|
||||
if (rootRole == 'toolbar' && items[i].id === focussedId) {
|
||||
items[i].focus();
|
||||
/**
|
||||
* Moves the focus to the up this is called by the up key.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function up() {
|
||||
moveFocus(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the focus to the up this is called by the down key.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function down() {
|
||||
var role = getRole(), parentRole = getParentRole();
|
||||
|
||||
if (role == "menuitem" && parentRole == "menubar") {
|
||||
enter();
|
||||
} else if (role == "button" && getAriaProp('haspopup')) {
|
||||
enter({key: 'down'});
|
||||
} else {
|
||||
moveFocus(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the focus to the next item or previous item depending on shift key.
|
||||
*
|
||||
* @private
|
||||
* @param {DOMEvent} e DOM event object.
|
||||
*/
|
||||
function tab(e) {
|
||||
var parentRole = getParentRole();
|
||||
|
||||
if (parentRole == "tablist") {
|
||||
var elm = getFocusElements(focusedControl.getEl('body'))[0];
|
||||
|
||||
if (elm) {
|
||||
elm.focus();
|
||||
}
|
||||
} else {
|
||||
moveFocus(e.shiftKey ? -1 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the cancel event on the currently focused control. This is normally done using the Esc key.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function cancel() {
|
||||
focusedControl.fire('cancel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} aria Optional aria data to pass along with the enter event.
|
||||
*/
|
||||
function enter(aria) {
|
||||
aria = aria || {};
|
||||
focusedControl.fire('click', {target: focusedElement, aria: aria});
|
||||
}
|
||||
|
||||
root.on('keydown', function(e) {
|
||||
function handleNonTabOrEscEvent(e, handler) {
|
||||
// Ignore non tab keys for text elements
|
||||
if (isTextInputElement(focusedElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler(e) !== false) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
items[0].focus();
|
||||
}
|
||||
|
||||
// Handle accessible keys
|
||||
root.on('keydown', function(e) {
|
||||
var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
|
||||
var DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32, DOM_VK_TAB = 9;
|
||||
var preventDefault;
|
||||
if (e.isDefaultPrevented()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case DOM_VK_LEFT:
|
||||
if (enableLeftRight) {
|
||||
if (settings.leftAction) {
|
||||
settings.leftAction();
|
||||
} else {
|
||||
moveFocus(-1);
|
||||
}
|
||||
|
||||
preventDefault = true;
|
||||
}
|
||||
case 37: // DOM_VK_LEFT
|
||||
handleNonTabOrEscEvent(e, left);
|
||||
break;
|
||||
|
||||
case DOM_VK_RIGHT:
|
||||
if (enableLeftRight) {
|
||||
if (getRole() == 'menuitem' && getParentRole() == 'menu') {
|
||||
if (getAriaProp('haspopup')) {
|
||||
action();
|
||||
}
|
||||
} else {
|
||||
moveFocus(1);
|
||||
}
|
||||
|
||||
preventDefault = true;
|
||||
}
|
||||
case 39: // DOM_VK_RIGHT
|
||||
handleNonTabOrEscEvent(e, right);
|
||||
break;
|
||||
|
||||
case DOM_VK_UP:
|
||||
if (enableUpDown) {
|
||||
moveFocus(-1);
|
||||
preventDefault = true;
|
||||
}
|
||||
case 38: // DOM_VK_UP
|
||||
handleNonTabOrEscEvent(e, up);
|
||||
break;
|
||||
|
||||
case DOM_VK_DOWN:
|
||||
if (enableUpDown) {
|
||||
if (getRole() == 'menuitem' && getParentRole() == 'menubar') {
|
||||
action();
|
||||
} else if (getRole() == 'button' && getAriaProp('haspopup')) {
|
||||
action();
|
||||
} else {
|
||||
moveFocus(1);
|
||||
}
|
||||
|
||||
preventDefault = true;
|
||||
}
|
||||
case 40: // DOM_VK_DOWN
|
||||
handleNonTabOrEscEvent(e, down);
|
||||
break;
|
||||
|
||||
case DOM_VK_TAB:
|
||||
preventDefault = true;
|
||||
|
||||
if (e.shiftKey) {
|
||||
moveFocus(-1);
|
||||
} else {
|
||||
moveFocus(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case DOM_VK_ESCAPE:
|
||||
preventDefault = true;
|
||||
case 27: // DOM_VK_ESCAPE
|
||||
cancel();
|
||||
break;
|
||||
|
||||
case DOM_VK_ENTER:
|
||||
case DOM_VK_RETURN:
|
||||
case DOM_VK_SPACE:
|
||||
preventDefault = action();
|
||||
case 14: // DOM_VK_ENTER
|
||||
case 13: // DOM_VK_RETURN
|
||||
case 32: // DOM_VK_SPACE
|
||||
handleNonTabOrEscEvent(e, enter);
|
||||
break;
|
||||
}
|
||||
|
||||
if (preventDefault) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
case 9: // DOM_VK_TAB
|
||||
if (tab(e) !== false) {
|
||||
e.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Init on focus in
|
||||
root.on('focusin', function(e) {
|
||||
initItems();
|
||||
focussedId = e.target.id;
|
||||
focusedElement = e.target;
|
||||
focusedControl = e.control;
|
||||
});
|
||||
|
||||
return {
|
||||
moveFocus: moveFocus,
|
||||
focusFirst: focusFirst,
|
||||
cancel: cancel
|
||||
focusFirst: focusFirst
|
||||
};
|
||||
};
|
||||
});
|
||||
Regular → Executable
+2
-2
@@ -116,10 +116,10 @@ define("tinymce/ui/Label", [
|
||||
var self = this, forId = self.settings.forId;
|
||||
|
||||
return (
|
||||
'<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId : '') + '">' +
|
||||
'<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
|
||||
self.encode(self._text) +
|
||||
'</label>'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+46
-20
@@ -29,17 +29,34 @@ define("tinymce/ui/ListBox", [
|
||||
* @setting {Array} values Array with values to add to list box.
|
||||
*/
|
||||
init: function(settings) {
|
||||
var self = this, values, i, selected, selectedText, lastItemCtrl;
|
||||
var self = this, values, selected, selectedText, lastItemCtrl;
|
||||
|
||||
function setSelected(menuValues) {
|
||||
// Try to find a selected value
|
||||
for (var i = 0; i < menuValues.length; i++) {
|
||||
selected = menuValues[i].selected || settings.value === menuValues[i].value;
|
||||
|
||||
if (selected) {
|
||||
selectedText = selectedText || menuValues[i].text;
|
||||
self._value = menuValues[i].value;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the value has a submenu, try to find the selected values in that menu
|
||||
if (menuValues[i].menu) {
|
||||
setSelected(menuValues[i].menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self._values = values = settings.values;
|
||||
if (values) {
|
||||
for (i = 0; i < values.length; i++) {
|
||||
selected = values[i].selected || settings.value === values[i].value;
|
||||
setSelected(values);
|
||||
|
||||
if (selected) {
|
||||
selectedText = selectedText || values[i].text;
|
||||
self._value = values[i].value;
|
||||
}
|
||||
// Default with first item
|
||||
if (!selected && values.length > 0) {
|
||||
selectedText = values[0].text;
|
||||
self._value = values[0].value;
|
||||
}
|
||||
|
||||
settings.menu = values;
|
||||
@@ -67,7 +84,6 @@ define("tinymce/ui/ListBox", [
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* disable/enable 某一list的item
|
||||
* leanote ace life ace
|
||||
@@ -92,6 +108,7 @@ define("tinymce/ui/ListBox", [
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter/setter function for the control value.
|
||||
*
|
||||
@@ -100,10 +117,10 @@ define("tinymce/ui/ListBox", [
|
||||
* @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
|
||||
*/
|
||||
value: function(value) {
|
||||
var self = this, active, selectedText, menu, i;
|
||||
var self = this, active, selectedText, menu;
|
||||
|
||||
function activateByValue(menu, value) {
|
||||
menu.items().each(function(ctrl) {// menuitem
|
||||
menu.items().each(function(ctrl) {
|
||||
active = ctrl.value() === value;
|
||||
|
||||
if (active) {
|
||||
@@ -118,24 +135,33 @@ define("tinymce/ui/ListBox", [
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof(value) != "undefined") {
|
||||
function setActiveValues(menuValues) {
|
||||
for (var i = 0; i < menuValues.length; i++) {
|
||||
active = menuValues[i].value == value;
|
||||
|
||||
if (active) {
|
||||
selectedText = selectedText || menuValues[i].text;
|
||||
}
|
||||
|
||||
menuValues[i].active = active;
|
||||
|
||||
if (menuValues[i].menu) {
|
||||
setActiveValues(menuValues[i].menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value != "undefined") {
|
||||
if (self.menu) {
|
||||
activateByValue(self.menu, value);
|
||||
} else {
|
||||
menu = self.settings.menu;
|
||||
for (i = 0; i < menu.length; i++) {
|
||||
active = menu[i].value == value;
|
||||
|
||||
if (active) {
|
||||
selectedText = selectedText || menu[i].text;
|
||||
}
|
||||
|
||||
menu[i].active = active;
|
||||
}
|
||||
setActiveValues(menu);
|
||||
}
|
||||
|
||||
self.text(selectedText || this.settings.text);
|
||||
}
|
||||
|
||||
return self._super(value);
|
||||
}
|
||||
});
|
||||
|
||||
Regular → Executable
+22
-21
@@ -17,10 +17,9 @@
|
||||
*/
|
||||
define("tinymce/ui/Menu", [
|
||||
"tinymce/ui/FloatPanel",
|
||||
"tinymce/ui/KeyboardNavigation",
|
||||
"tinymce/ui/MenuItem",
|
||||
"tinymce/util/Tools"
|
||||
], function(FloatPanel, KeyboardNavigation, MenuItem, Tools) {
|
||||
], function(FloatPanel, MenuItem, Tools) {
|
||||
"use strict";
|
||||
|
||||
var Menu = FloatPanel.extend({
|
||||
@@ -28,7 +27,9 @@ define("tinymce/ui/Menu", [
|
||||
defaultType: 'menuitem',
|
||||
border: 1,
|
||||
layout: 'stack',
|
||||
role: 'menu'
|
||||
role: 'application',
|
||||
bodyRole: 'menu',
|
||||
ariaRoot: true
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -53,23 +54,6 @@ define("tinymce/ui/Menu", [
|
||||
|
||||
self._super(settings);
|
||||
self.addClass('menu');
|
||||
|
||||
self.keyNav = new KeyboardNavigation({
|
||||
root: self,
|
||||
enableUpDown: true,
|
||||
enableLeftRight: true,
|
||||
|
||||
leftAction: function() {
|
||||
if (self.parent() instanceof MenuItem) {
|
||||
self.keyNav.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
onCancel: function() {
|
||||
self.fire('cancel', {}, false);
|
||||
self.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -97,7 +81,6 @@ define("tinymce/ui/Menu", [
|
||||
var self = this;
|
||||
|
||||
self.hideAll();
|
||||
self.fire('cancel');
|
||||
self.fire('select');
|
||||
},
|
||||
|
||||
@@ -113,7 +96,25 @@ define("tinymce/ui/Menu", [
|
||||
|
||||
return self._super();
|
||||
},
|
||||
/*
|
||||
getContainerElm: function() {
|
||||
var doc = document, id = this.classPrefix + 'menucontainer';
|
||||
|
||||
var elm = doc.getElementById(id);
|
||||
if (!elm) {
|
||||
elm = doc.createElement('div');
|
||||
elm.id = id;
|
||||
elm.setAttribute('role', 'application');
|
||||
elm.className = this.classPrefix + '-reset';
|
||||
elm.style.position = 'absolute';
|
||||
elm.style.top = elm.style.left = '0';
|
||||
elm.style.overflow = 'visible';
|
||||
doc.body.appendChild(elm);
|
||||
}
|
||||
|
||||
return elm;
|
||||
},
|
||||
*/
|
||||
/**
|
||||
* Invoked before the menu is rendered.
|
||||
*
|
||||
|
||||
Regular → Executable
+2
-1
@@ -13,7 +13,7 @@
|
||||
*
|
||||
* @-x-less MenuBar.less
|
||||
* @class tinymce.ui.MenuBar
|
||||
* @extends tinymce.ui.Container
|
||||
* @extends tinymce.ui.Toolbar
|
||||
*/
|
||||
define("tinymce/ui/MenuBar", [
|
||||
"tinymce/ui/Toolbar"
|
||||
@@ -24,6 +24,7 @@ define("tinymce/ui/MenuBar", [
|
||||
Defaults: {
|
||||
role: 'menubar',
|
||||
containerCls: 'menubar',
|
||||
ariaRoot: true,
|
||||
defaults: {
|
||||
type: 'menubutton'
|
||||
}
|
||||
|
||||
Regular → Executable
+32
-10
@@ -83,22 +83,29 @@ define("tinymce/ui/MenuButton", [
|
||||
menu.type = menu.type || 'menu';
|
||||
}
|
||||
|
||||
self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
|
||||
self.menu = Factory.create(menu).parent(self).renderTo();
|
||||
self.fire('createmenu');
|
||||
self.menu.reflow();
|
||||
self.menu.on('cancel', function(e) {
|
||||
if (e.control === self.menu) {
|
||||
if (e.control.parent() === self.menu) {
|
||||
e.stopPropagation();
|
||||
self.focus();
|
||||
self.hideMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Move focus to button when a menu item is selected/clicked
|
||||
self.menu.on('select', function() {
|
||||
self.focus();
|
||||
});
|
||||
|
||||
self.menu.on('show hide', function(e) {
|
||||
if (e.control == self.menu) {
|
||||
self.activeMenu(e.type == 'show');
|
||||
}
|
||||
}).fire('show');
|
||||
|
||||
self.aria('expanded', true);
|
||||
self.aria('expanded', e.type == 'show');
|
||||
}).fire('show');
|
||||
}
|
||||
|
||||
self.menu.show();
|
||||
@@ -122,7 +129,6 @@ define("tinymce/ui/MenuButton", [
|
||||
});
|
||||
|
||||
self.menu.hide();
|
||||
self.aria('expanded', false);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -143,14 +149,30 @@ define("tinymce/ui/MenuButton", [
|
||||
*/
|
||||
renderHtml: function() {
|
||||
var self = this, id = self._id, prefix = self.classPrefix;
|
||||
var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
|
||||
var icon = self.settings.icon, image;
|
||||
|
||||
image = self.settings.image;
|
||||
if (image) {
|
||||
icon = 'none';
|
||||
|
||||
// Support for [high dpi, low dpi] image sources
|
||||
if (typeof image != "string") {
|
||||
image = window.getSelection ? image[0] : image[1];
|
||||
}
|
||||
|
||||
image = ' style="background-image: url(\'' + image + '\')"';
|
||||
} else {
|
||||
image = '';
|
||||
}
|
||||
|
||||
icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
|
||||
|
||||
self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
|
||||
'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
|
||||
'<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
|
||||
(icon ? '<i class="' + icon + '"></i>' : '') +
|
||||
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
|
||||
'<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
|
||||
' <i class="' + prefix + 'caret"></i>' +
|
||||
'</button>' +
|
||||
@@ -170,7 +192,7 @@ define("tinymce/ui/MenuButton", [
|
||||
if (e.control === self && isChildOf(e.target, self.getEl())) {
|
||||
self.showMenu();
|
||||
|
||||
if (e.keyboard) {
|
||||
if (e.aria) {
|
||||
self.menu.items()[0].focus();
|
||||
}
|
||||
}
|
||||
@@ -213,7 +235,7 @@ define("tinymce/ui/MenuButton", [
|
||||
if (self._rendered) {
|
||||
children = self.getEl('open').getElementsByTagName('span');
|
||||
for (i = 0; i < children.length; i++) {
|
||||
children[i].innerHTML = self.encode(text);
|
||||
children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Regular → Executable
+80
-33
@@ -13,12 +13,13 @@
|
||||
*
|
||||
* @-x-less MenuItem.less
|
||||
* @class tinymce.ui.MenuItem
|
||||
* @extends tinymce.ui.Control
|
||||
* @extends tinymce.ui.Widget
|
||||
*/
|
||||
define("tinymce/ui/MenuItem", [
|
||||
"tinymce/ui/Widget",
|
||||
"tinymce/ui/Factory"
|
||||
], function(Widget, Factory) {
|
||||
"tinymce/ui/Factory",
|
||||
"tinymce/Env"
|
||||
], function(Widget, Factory, Env) {
|
||||
"use strict";
|
||||
|
||||
return Widget.extend({
|
||||
@@ -58,13 +59,11 @@ define("tinymce/ui/MenuItem", [
|
||||
if (self._text === '-' || self._text === '|') {
|
||||
self.addClass('menu-item-sep');
|
||||
self.aria('role', 'separator');
|
||||
self.canFocus = false;
|
||||
self._text = '-';
|
||||
}
|
||||
|
||||
if (settings.selectable) {
|
||||
self.aria('role', 'menuitemcheckbox');
|
||||
self.aria('checked', true);
|
||||
self.addClass('menu-item-checkbox');
|
||||
settings.icon = 'selected';
|
||||
}
|
||||
@@ -77,25 +76,7 @@ define("tinymce/ui/MenuItem", [
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
self.on('mouseenter click', function(e) {
|
||||
if (e.control === self) {
|
||||
if (!settings.menu && e.type === 'click') {
|
||||
self.parent().hideAll();
|
||||
self.fire('cancel');
|
||||
self.fire('select');
|
||||
} else {
|
||||
self.showMenu();
|
||||
|
||||
if (e.keyboard) {
|
||||
setTimeout(function() {
|
||||
self.menu.items()[0].focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (settings.menu) {
|
||||
if (settings.menu && !settings.ariaHideMenu) {
|
||||
self.aria('haspopup', true);
|
||||
}
|
||||
},
|
||||
@@ -144,18 +125,26 @@ define("tinymce/ui/MenuItem", [
|
||||
menu.itemDefaults = parent.settings.itemDefaults;
|
||||
}
|
||||
|
||||
menu = self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
|
||||
menu = self.menu = Factory.create(menu).parent(self).renderTo();
|
||||
menu.reflow();
|
||||
menu.fire('show');
|
||||
menu.on('cancel', function() {
|
||||
menu.on('cancel', function(e) {
|
||||
e.stopPropagation();
|
||||
self.focus();
|
||||
menu.hide();
|
||||
});
|
||||
menu.on('show hide', function(e) {
|
||||
e.control.items().each(function(ctrl) {
|
||||
ctrl.active(ctrl.settings.selected);
|
||||
});
|
||||
}).fire('show');
|
||||
|
||||
menu.on('hide', function(e) {
|
||||
if (e.control === menu) {
|
||||
self.removeClass('selected');
|
||||
}
|
||||
});
|
||||
|
||||
menu.submenu = true;
|
||||
} else {
|
||||
menu.show();
|
||||
}
|
||||
@@ -212,7 +201,37 @@ define("tinymce/ui/MenuItem", [
|
||||
*/
|
||||
renderHtml: function() {
|
||||
var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
|
||||
var icon = self.settings.icon, image = '';
|
||||
var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
|
||||
|
||||
// Converts shortcut format to Mac/PC variants
|
||||
function convertShortcut(shortcut) {
|
||||
var i, value, replace = {};
|
||||
|
||||
if (Env.mac) {
|
||||
replace = {
|
||||
alt: '⌥',
|
||||
ctrl: '⌘',
|
||||
shift: '⇧',
|
||||
meta: '⌘'
|
||||
};
|
||||
} else {
|
||||
replace = {
|
||||
meta: 'Ctrl'
|
||||
};
|
||||
}
|
||||
|
||||
shortcut = shortcut.split('+');
|
||||
|
||||
for (i = 0; i < shortcut.length; i++) {
|
||||
value = replace[shortcut[i].toLowerCase()];
|
||||
|
||||
if (value) {
|
||||
shortcut[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return shortcut.join('+');
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
self.parent().addClass('menu-has-icons');
|
||||
@@ -223,14 +242,17 @@ define("tinymce/ui/MenuItem", [
|
||||
image = ' style="background-image: url(\'' + settings.image + '\')"';
|
||||
}
|
||||
|
||||
if (shortcut) {
|
||||
shortcut = convertShortcut(shortcut);
|
||||
}
|
||||
|
||||
icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
|
||||
(text !== '-' ? '<i class="' + icon + '"' + image + '></i> ' : '') +
|
||||
(text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
|
||||
(text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
|
||||
(settings.shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' +
|
||||
settings.shortcut + '</div>' : '') +
|
||||
(shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
|
||||
(settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
|
||||
'</div>'
|
||||
);
|
||||
@@ -245,7 +267,7 @@ define("tinymce/ui/MenuItem", [
|
||||
var self = this, settings = self.settings;
|
||||
|
||||
var textStyle = settings.textStyle;
|
||||
if (typeof(textStyle) == "function") {
|
||||
if (typeof textStyle == "function") {
|
||||
textStyle = textStyle.call(this);
|
||||
}
|
||||
|
||||
@@ -256,7 +278,32 @@ define("tinymce/ui/MenuItem", [
|
||||
}
|
||||
}
|
||||
|
||||
return self._super();
|
||||
self.on('mouseenter click', function(e) {
|
||||
if (e.control === self) {
|
||||
if (!settings.menu && e.type === 'click') {
|
||||
self.fire('select');
|
||||
self.parent().hideAll();
|
||||
} else {
|
||||
self.showMenu();
|
||||
|
||||
if (e.aria) {
|
||||
self.menu.focus(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self._super();
|
||||
|
||||
return self;
|
||||
},
|
||||
|
||||
active: function(state) {
|
||||
if (typeof state != "undefined") {
|
||||
this.aria('checked', state);
|
||||
}
|
||||
|
||||
return this._super(state);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Regular → Executable
+33
-29
@@ -11,8 +11,8 @@
|
||||
/**
|
||||
* This class is used to create MessageBoxes like alerts/confirms etc.
|
||||
*
|
||||
* @class tinymce.ui.Window
|
||||
* @extends tinymce.ui.FloatPanel
|
||||
* @class tinymce.ui.MessageBox
|
||||
* @extends tinymce.ui.Window
|
||||
*/
|
||||
define("tinymce/ui/MessageBox", [
|
||||
"tinymce/ui/Window"
|
||||
@@ -94,44 +94,41 @@ define("tinymce/ui/MessageBox", [
|
||||
msgBox: function(settings) {
|
||||
var buttons, callback = settings.callback || function() {};
|
||||
|
||||
function createButton(text, status, primary) {
|
||||
return {
|
||||
type: "button",
|
||||
text: text,
|
||||
subtype: primary ? 'primary' : '',
|
||||
onClick: function(e) {
|
||||
e.control.parents()[1].close();
|
||||
callback(status);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
switch (settings.buttons) {
|
||||
case MessageBox.OK_CANCEL:
|
||||
buttons = [
|
||||
{type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
|
||||
e.control.parents()[1].close();
|
||||
callback(true);
|
||||
}},
|
||||
|
||||
{type: "button", text: "Cancel", onClick: function(e) {
|
||||
e.control.parents()[1].close();
|
||||
callback(false);
|
||||
}}
|
||||
createButton('Ok', true, true),
|
||||
createButton('Cancel', false)
|
||||
];
|
||||
break;
|
||||
|
||||
case MessageBox.YES_NO:
|
||||
buttons = [
|
||||
{type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
|
||||
e.control.parents()[1].close();
|
||||
callback(true);
|
||||
}}
|
||||
];
|
||||
break;
|
||||
|
||||
case MessageBox.YES_NO_CANCEL:
|
||||
buttons = [
|
||||
{type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
|
||||
e.control.parents()[1].close();
|
||||
}}
|
||||
createButton('Yes', 1, true),
|
||||
createButton('No', 0)
|
||||
];
|
||||
|
||||
if (settings.buttons == MessageBox.YES_NO_CANCEL) {
|
||||
buttons.push(createButton('Cancel', -1));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
buttons = [
|
||||
{type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
|
||||
e.control.parents()[1].close();
|
||||
callback(true);
|
||||
}}
|
||||
createButton('Ok', true, true)
|
||||
];
|
||||
break;
|
||||
}
|
||||
@@ -147,6 +144,7 @@ define("tinymce/ui/MessageBox", [
|
||||
align: "center",
|
||||
buttons: buttons,
|
||||
title: settings.title,
|
||||
role: 'alertdialog',
|
||||
items: {
|
||||
type: "label",
|
||||
multiline: true,
|
||||
@@ -154,7 +152,13 @@ define("tinymce/ui/MessageBox", [
|
||||
maxHeight: 200,
|
||||
text: settings.text
|
||||
},
|
||||
onClose: settings.onClose
|
||||
onPostRender: function() {
|
||||
this.aria('describedby', this.items()[0]._id);
|
||||
},
|
||||
onClose: settings.onClose,
|
||||
onCancel: function() {
|
||||
callback(false);
|
||||
}
|
||||
}).renderTo(document.body).reflow();
|
||||
},
|
||||
|
||||
@@ -166,7 +170,7 @@ define("tinymce/ui/MessageBox", [
|
||||
* @param {function} [callback] Callback to execute when the user makes a choice.
|
||||
*/
|
||||
alert: function(settings, callback) {
|
||||
if (typeof(settings) == "string") {
|
||||
if (typeof settings == "string") {
|
||||
settings = {text: settings};
|
||||
}
|
||||
|
||||
@@ -182,7 +186,7 @@ define("tinymce/ui/MessageBox", [
|
||||
* @param {function} [callback] Callback to execute when the user makes a choice.
|
||||
*/
|
||||
confirm: function(settings, callback) {
|
||||
if (typeof(settings) == "string") {
|
||||
if (typeof settings == "string") {
|
||||
settings = {text: settings};
|
||||
}
|
||||
|
||||
|
||||
Regular → Executable
+2
-2
@@ -28,7 +28,7 @@ define("tinymce/ui/Movable", [
|
||||
x = pos.x;
|
||||
y = pos.y;
|
||||
|
||||
if (ctrl._fixed) {
|
||||
if (ctrl._fixed && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') {
|
||||
x -= viewport.x;
|
||||
y -= viewport.y;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ define("tinymce/ui/Movable", [
|
||||
* @return {tinymce.ui.Control} Current control instance.
|
||||
*/
|
||||
moveRel: function(elm, rel) {
|
||||
if (typeof(rel) != 'string') {
|
||||
if (typeof rel != 'string') {
|
||||
rel = this.testMoveRel(elm, rel);
|
||||
}
|
||||
|
||||
|
||||
Regular → Executable
+3
-3
@@ -42,14 +42,14 @@ define("tinymce/ui/Panel", [
|
||||
self.preRender();
|
||||
layout.preRender(self);
|
||||
|
||||
if (typeof(innerHtml) == "undefined") {
|
||||
if (typeof innerHtml == "undefined") {
|
||||
innerHtml = (
|
||||
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
|
||||
layout.renderHtml(self) +
|
||||
'</div>'
|
||||
);
|
||||
} else {
|
||||
if (typeof(innerHtml) == 'function') {
|
||||
if (typeof innerHtml == 'function') {
|
||||
innerHtml = innerHtml.call(self);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ define("tinymce/ui/Panel", [
|
||||
}
|
||||
|
||||
return (
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
|
||||
(self._preBodyHtml || '') +
|
||||
innerHtml +
|
||||
'</div>'
|
||||
|
||||
Regular → Executable
+19
@@ -43,12 +43,19 @@ define("tinymce/ui/PanelButton", [
|
||||
};
|
||||
}
|
||||
|
||||
panelSettings.role = panelSettings.role || 'dialog';
|
||||
panelSettings.popover = true;
|
||||
panelSettings.autohide = true;
|
||||
panelSettings.ariaRoot = true;
|
||||
|
||||
self.panel = new FloatPanel(panelSettings).on('hide', function() {
|
||||
self.active(false);
|
||||
}).on('cancel', function(e) {
|
||||
e.stopPropagation();
|
||||
self.focus();
|
||||
self.hidePanel();
|
||||
}).parent(self).renderTo(self.getContainerElm());
|
||||
|
||||
self.panel.fire('show');
|
||||
self.panel.reflow();
|
||||
} else {
|
||||
@@ -79,17 +86,29 @@ define("tinymce/ui/PanelButton", [
|
||||
postRender: function() {
|
||||
var self = this;
|
||||
|
||||
self.aria('haspopup', true);
|
||||
|
||||
self.on('click', function(e) {
|
||||
if (e.control === self) {
|
||||
if (self.panel && self.panel.visible()) {
|
||||
self.hidePanel();
|
||||
} else {
|
||||
self.showPanel();
|
||||
self.panel.focus(!!e.aria);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return self._super();
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
if (this.panel) {
|
||||
this.panel.remove();
|
||||
this.panel = null;
|
||||
}
|
||||
|
||||
return this._super();
|
||||
}
|
||||
});
|
||||
});
|
||||
Regular → Executable
+7
-13
@@ -16,9 +16,8 @@
|
||||
* @extends tinymce.ui.Widget
|
||||
*/
|
||||
define("tinymce/ui/Path", [
|
||||
"tinymce/ui/Widget",
|
||||
"tinymce/ui/KeyboardNavigation"
|
||||
], function(Widget, KeyboardNavigation) {
|
||||
"tinymce/ui/Widget"
|
||||
], function(Widget) {
|
||||
"use strict";
|
||||
|
||||
return Widget.extend({
|
||||
@@ -58,12 +57,7 @@ define("tinymce/ui/Path", [
|
||||
focus: function() {
|
||||
var self = this;
|
||||
|
||||
self.keyNav = new KeyboardNavigation({
|
||||
root: self,
|
||||
enableLeftRight: true
|
||||
});
|
||||
|
||||
self.keyNav.focusFirst();
|
||||
self.getEl().firstChild.focus();
|
||||
|
||||
return self;
|
||||
},
|
||||
@@ -77,7 +71,7 @@ define("tinymce/ui/Path", [
|
||||
data: function(data) {
|
||||
var self = this;
|
||||
|
||||
if (typeof(data) !== "undefined") {
|
||||
if (typeof data !== "undefined") {
|
||||
self._data = data;
|
||||
self.update();
|
||||
|
||||
@@ -130,14 +124,14 @@ define("tinymce/ui/Path", [
|
||||
|
||||
for (i = 0, l = parts.length; i < l; i++) {
|
||||
html += (
|
||||
(i > 0 ? '<div class="'+ prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
|
||||
(i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
|
||||
'<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
|
||||
i + '" tabindex="-1" id="' + self._id + '-' + i +'">' + parts[i].name + '</div>'
|
||||
i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
|
||||
);
|
||||
}
|
||||
|
||||
if (!html) {
|
||||
html = '<div class="' + prefix + 'path-item"> </div>';
|
||||
html = '<div class="' + prefix + 'path-item">\u00a0</div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+3
-3
@@ -19,7 +19,7 @@ define("tinymce/ui/Resizable", [
|
||||
"use strict";
|
||||
|
||||
return {
|
||||
/**
|
||||
/**
|
||||
* Resizes the control to contents.
|
||||
*
|
||||
* @method resizeToContent
|
||||
@@ -30,7 +30,7 @@ define("tinymce/ui/Resizable", [
|
||||
this.reflow();
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Resizes the control to a specific width/height.
|
||||
*
|
||||
* @method resizeTo
|
||||
@@ -51,7 +51,7 @@ define("tinymce/ui/Resizable", [
|
||||
return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Resizes the control to a specific relative width/height.
|
||||
*
|
||||
* @method resizeBy
|
||||
|
||||
Regular → Executable
+1
-1
@@ -69,7 +69,7 @@ define("tinymce/ui/ResizeHandle", [
|
||||
self.fire('Resize', e);
|
||||
},
|
||||
|
||||
end: function() {
|
||||
stop: function() {
|
||||
self.fire('ResizeEnd');
|
||||
}
|
||||
});
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+3
@@ -8,6 +8,8 @@
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/*eslint no-nested-ternary:0 */
|
||||
|
||||
/**
|
||||
* Selector engine, enables you to select controls by using CSS like expressions.
|
||||
* We currently only support basic CSS expressions to reduce the size of the core
|
||||
@@ -72,6 +74,7 @@ define("tinymce/ui/Selector", [
|
||||
var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
|
||||
|
||||
/*jshint maxlen:255 */
|
||||
/*eslint max-len:0 */
|
||||
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
|
||||
whiteSpace = /^\s*|\s*$/g,
|
||||
Collection;
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+25
-9
@@ -13,7 +13,7 @@
|
||||
*
|
||||
* @-x-less SplitButton.less
|
||||
* @class tinymce.ui.SplitButton
|
||||
* @extends tinymce.ui.Button
|
||||
* @extends tinymce.ui.MenuButton
|
||||
*/
|
||||
define("tinymce/ui/SplitButton", [
|
||||
"tinymce/ui/MenuButton",
|
||||
@@ -22,7 +22,7 @@ define("tinymce/ui/SplitButton", [
|
||||
return MenuButton.extend({
|
||||
Defaults: {
|
||||
classes: "widget btn splitbtn",
|
||||
role: "splitbutton"
|
||||
role: "button"
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -68,16 +68,32 @@ define("tinymce/ui/SplitButton", [
|
||||
* @return {String} HTML representing the control.
|
||||
*/
|
||||
renderHtml: function() {
|
||||
var self = this, id = self._id, prefix = self.classPrefix;
|
||||
var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
|
||||
var self = this, id = self._id, prefix = self.classPrefix, image;
|
||||
var icon = self.settings.icon;
|
||||
|
||||
image = self.settings.image;
|
||||
if (image) {
|
||||
icon = 'none';
|
||||
|
||||
// Support for [high dpi, low dpi] image sources
|
||||
if (typeof image != "string") {
|
||||
image = window.getSelection ? image[0] : image[1];
|
||||
}
|
||||
|
||||
image = ' style="background-image: url(\'' + image + '\')"';
|
||||
} else {
|
||||
image = '';
|
||||
}
|
||||
|
||||
icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '">' +
|
||||
'<button type="button" hidefocus tabindex="-1">' +
|
||||
(icon ? '<i class="' + icon + '"></i>' : '') +
|
||||
'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
|
||||
'<button type="button" hidefocus="1" tabindex="-1">' +
|
||||
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
|
||||
(self._text ? (icon ? ' ' : '') + self._text : '') +
|
||||
'</button>' +
|
||||
'<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' +
|
||||
'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
|
||||
//(icon ? '<i class="' + icon + '"></i>' : '') +
|
||||
(self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
|
||||
' <i class="' + prefix + 'caret"></i>' +
|
||||
@@ -100,7 +116,7 @@ define("tinymce/ui/SplitButton", [
|
||||
if (e.control == this) {
|
||||
// Find clicks that is on the main button
|
||||
while (node) {
|
||||
if (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1) {
|
||||
if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
|
||||
e.stopImmediatePropagation();
|
||||
onClickHandler.call(this, e);
|
||||
return;
|
||||
|
||||
Regular → Executable
+1
-1
@@ -24,7 +24,7 @@ define("tinymce/ui/StackLayout", [
|
||||
Defaults: {
|
||||
containerClass: 'stack-layout',
|
||||
controlClass: 'stack-layout-item',
|
||||
endClass : 'break'
|
||||
endClass: 'break'
|
||||
}
|
||||
});
|
||||
});
|
||||
Regular → Executable
+25
-16
@@ -24,8 +24,6 @@ define("tinymce/ui/TabPanel", [
|
||||
"use strict";
|
||||
|
||||
return Panel.extend({
|
||||
lastIdx: 0,
|
||||
|
||||
Defaults: {
|
||||
layout: 'absolute',
|
||||
defaults: {
|
||||
@@ -40,21 +38,28 @@ define("tinymce/ui/TabPanel", [
|
||||
* @param {Number} idx Index of the tab to activate.
|
||||
*/
|
||||
activateTab: function(idx) {
|
||||
var activeTabElm;
|
||||
|
||||
if (this.activeTabId) {
|
||||
DomUtils.removeClass(this.getEl(this.activeTabId), this.classPrefix + 'active');
|
||||
activeTabElm = this.getEl(this.activeTabId);
|
||||
DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
|
||||
activeTabElm.setAttribute('aria-selected', "false");
|
||||
}
|
||||
|
||||
this.activeTabId = 't' + idx;
|
||||
|
||||
DomUtils.addClass(this.getEl('t' + idx), this.classPrefix + 'active');
|
||||
|
||||
if (idx != this.lastIdx) {
|
||||
this.items()[this.lastIdx].hide();
|
||||
this.lastIdx = idx;
|
||||
}
|
||||
activeTabElm = this.getEl('t' + idx);
|
||||
activeTabElm.setAttribute('aria-selected', "true");
|
||||
DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
|
||||
|
||||
this.items()[idx].show().fire('showtab');
|
||||
this.reflow();
|
||||
|
||||
this.items().each(function(item, i) {
|
||||
if (idx != i) {
|
||||
item.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -70,16 +75,22 @@ define("tinymce/ui/TabPanel", [
|
||||
layout.preRender(self);
|
||||
|
||||
self.items().each(function(ctrl, i) {
|
||||
var id = self._id + '-t' + i;
|
||||
|
||||
ctrl.aria('role', 'tabpanel');
|
||||
ctrl.aria('labelledby', id);
|
||||
|
||||
tabsHtml += (
|
||||
'<div id="' + self._id + '-t' + i + '" class="' + prefix + 'tab" unselectable="on">' +
|
||||
'<div id="' + id + '" class="' + prefix + 'tab" ' +
|
||||
'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
|
||||
self.encode(ctrl.settings.title) +
|
||||
'</div>'
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
|
||||
'<div id="' + self._id + '-head" class="' + prefix + 'tabs">' +
|
||||
'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
|
||||
'<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
|
||||
tabsHtml +
|
||||
'</div>' +
|
||||
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
|
||||
@@ -131,12 +142,10 @@ define("tinymce/ui/TabPanel", [
|
||||
minW = DomUtils.getSize(self.getEl('head')).width;
|
||||
minW = minW < 0 ? 0 : minW;
|
||||
minH = 0;
|
||||
self.items().each(function(item, i) {
|
||||
|
||||
self.items().each(function(item) {
|
||||
minW = Math.max(minW, item.layoutRect().minW);
|
||||
minH = Math.max(minH, item.layoutRect().minH);
|
||||
if (self.settings.activeTab != i) {
|
||||
item.hide();
|
||||
}
|
||||
});
|
||||
|
||||
self.items().each(function(ctrl) {
|
||||
|
||||
Regular → Executable
+4
-4
@@ -68,7 +68,7 @@ define("tinymce/ui/TextBox", [
|
||||
disabled: function(state) {
|
||||
var self = this;
|
||||
|
||||
if (self._rendered && typeof(state) != 'undefined') {
|
||||
if (self._rendered && typeof state != 'undefined') {
|
||||
self.getEl().disabled = state;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ define("tinymce/ui/TextBox", [
|
||||
value: function(value) {
|
||||
var self = this;
|
||||
|
||||
if (typeof(value) != "undefined") {
|
||||
if (typeof value != "undefined") {
|
||||
self._value = value;
|
||||
|
||||
if (self._rendered) {
|
||||
@@ -183,12 +183,12 @@ define("tinymce/ui/TextBox", [
|
||||
return (
|
||||
'<textarea id="' + id + '" class="' + self.classes() + '" ' +
|
||||
(settings.rows ? ' rows="' + settings.rows + '"' : '') +
|
||||
' hidefocus="true"' + extraAttrs + '>' + value +
|
||||
' hidefocus="1"' + extraAttrs + '>' + value +
|
||||
'</textarea>'
|
||||
);
|
||||
}
|
||||
|
||||
return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="true"' + extraAttrs + '>';
|
||||
return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Regular → Executable
+9
-5
@@ -15,8 +15,9 @@
|
||||
* @class tinymce.ui.Throbber
|
||||
*/
|
||||
define("tinymce/ui/Throbber", [
|
||||
"tinymce/ui/DomUtils"
|
||||
], function(DomUtils) {
|
||||
"tinymce/ui/DomUtils",
|
||||
"tinymce/ui/Control"
|
||||
], function(DomUtils, Control) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@@ -24,9 +25,10 @@ define("tinymce/ui/Throbber", [
|
||||
*
|
||||
* @constructor
|
||||
* @param {Element} elm DOM Html element to display throbber in.
|
||||
* @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
|
||||
*/
|
||||
return function(elm) {
|
||||
var self = this, state;
|
||||
return function(elm, inline) {
|
||||
var self = this, state, classPrefix = Control.classPrefix;
|
||||
|
||||
/**
|
||||
* Shows the throbber.
|
||||
@@ -42,7 +44,9 @@ define("tinymce/ui/Throbber", [
|
||||
|
||||
window.setTimeout(function() {
|
||||
if (state) {
|
||||
elm.appendChild(DomUtils.createFragment('<div class="mce-throbber"></div>'));
|
||||
elm.appendChild(DomUtils.createFragment(
|
||||
'<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
|
||||
));
|
||||
}
|
||||
}, time || 0);
|
||||
|
||||
|
||||
Regular → Executable
+2
-8
@@ -15,9 +15,8 @@
|
||||
* @extends tinymce.ui.Container
|
||||
*/
|
||||
define("tinymce/ui/Toolbar", [
|
||||
"tinymce/ui/Container",
|
||||
"tinymce/ui/KeyboardNavigation"
|
||||
], function(Container, KeyboardNavigation) {
|
||||
"tinymce/ui/Container"
|
||||
], function(Container) {
|
||||
"use strict";
|
||||
|
||||
return Container.extend({
|
||||
@@ -49,11 +48,6 @@ define("tinymce/ui/Toolbar", [
|
||||
|
||||
self.items().addClass('toolbar-item');
|
||||
|
||||
self.keyNav = new KeyboardNavigation({
|
||||
root: self,
|
||||
enableLeftRight: true
|
||||
});
|
||||
|
||||
return self._super();
|
||||
}
|
||||
});
|
||||
|
||||
Regular → Executable
+1
-1
@@ -37,7 +37,7 @@ define("tinymce/ui/Tooltip", [
|
||||
text: function(value) {
|
||||
var self = this;
|
||||
|
||||
if (typeof(value) != "undefined") {
|
||||
if (typeof value != "undefined") {
|
||||
self._value = value;
|
||||
|
||||
if (self._rendered) {
|
||||
|
||||
Regular → Executable
+10
-9
@@ -36,13 +36,14 @@ define("tinymce/ui/Widget", [
|
||||
var self = this;
|
||||
|
||||
self._super(settings);
|
||||
settings = self.settings;
|
||||
self.canFocus = true;
|
||||
|
||||
if (settings.tooltip && Widget.tooltips !== false) {
|
||||
self.on('mouseenter mouseleave', function(e) {
|
||||
self.on('mouseenter', function(e) {
|
||||
var tooltip = self.tooltip().moveTo(-0xFFFF);
|
||||
|
||||
if (e.control == self && e.type == 'mouseenter') {
|
||||
if (e.control == self) {
|
||||
var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
|
||||
|
||||
tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
|
||||
@@ -54,9 +55,13 @@ define("tinymce/ui/Widget", [
|
||||
tooltip.hide();
|
||||
}
|
||||
});
|
||||
|
||||
self.on('mouseleave mousedown click', function() {
|
||||
self.tooltip().hide();
|
||||
});
|
||||
}
|
||||
|
||||
self.aria('label', settings.tooltip);
|
||||
self.aria('label', settings.ariaLabel || settings.tooltip);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -66,11 +71,9 @@ define("tinymce/ui/Widget", [
|
||||
* @return {tinymce.ui.Tooltip} Tooltip instance.
|
||||
*/
|
||||
tooltip: function() {
|
||||
var self = this;
|
||||
|
||||
if (!tooltip) {
|
||||
tooltip = new Tooltip({type: 'tooltip'});
|
||||
tooltip.renderTo(self.getContainerElm());
|
||||
tooltip.renderTo();
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
@@ -130,9 +133,7 @@ define("tinymce/ui/Widget", [
|
||||
}
|
||||
|
||||
if (settings.autofocus) {
|
||||
setTimeout(function() {
|
||||
self.focus();
|
||||
}, 0);
|
||||
self.focus();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Regular → Executable
+35
-53
@@ -19,9 +19,8 @@ define("tinymce/ui/Window", [
|
||||
"tinymce/ui/FloatPanel",
|
||||
"tinymce/ui/Panel",
|
||||
"tinymce/ui/DomUtils",
|
||||
"tinymce/ui/KeyboardNavigation",
|
||||
"tinymce/ui/DragHelper"
|
||||
], function(FloatPanel, Panel, DomUtils, KeyboardNavigation, DragHelper) {
|
||||
], function(FloatPanel, Panel, DomUtils, DragHelper) {
|
||||
"use strict";
|
||||
|
||||
var Window = FloatPanel.extend({
|
||||
@@ -86,6 +85,11 @@ define("tinymce/ui/Window", [
|
||||
}
|
||||
});
|
||||
|
||||
self.on('cancel', function() {
|
||||
self.close();
|
||||
});
|
||||
|
||||
self.aria('describedby', self.describedBy || self._id + '-none');
|
||||
self.aria('label', settings.title);
|
||||
self._fullscreen = false;
|
||||
},
|
||||
@@ -193,8 +197,8 @@ define("tinymce/ui/Window", [
|
||||
if (settings.title) {
|
||||
headerHtml = (
|
||||
'<div id="' + id + '-head" class="' + prefix + 'window-head">' +
|
||||
'<div class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
|
||||
'<button type="button" class="' + prefix + 'close" aria-hidden="true">×</button>' +
|
||||
'<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
|
||||
'<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
|
||||
'<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
@@ -204,7 +208,7 @@ define("tinymce/ui/Window", [
|
||||
html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
|
||||
}
|
||||
|
||||
if (typeof(html) == "undefined") {
|
||||
if (typeof html == "undefined") {
|
||||
html = layout.renderHtml(self);
|
||||
}
|
||||
|
||||
@@ -213,12 +217,14 @@ define("tinymce/ui/Window", [
|
||||
}
|
||||
|
||||
return (
|
||||
'<div id="' + id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
|
||||
headerHtml +
|
||||
'<div id="' + id + '-body" class="' + self.classes('body') + '">' +
|
||||
html +
|
||||
'<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
|
||||
'<div class="' + self.classPrefix + 'reset" role="application">' +
|
||||
headerHtml +
|
||||
'<div id="' + id + '-body" class="' + self.classes('body') + '">' +
|
||||
html +
|
||||
'</div>' +
|
||||
footerHtml +
|
||||
'</div>' +
|
||||
footerHtml +
|
||||
'</div>'
|
||||
);
|
||||
},
|
||||
@@ -296,59 +302,19 @@ define("tinymce/ui/Window", [
|
||||
* @method postRender
|
||||
*/
|
||||
postRender: function() {
|
||||
var self = this, items = [], focusCtrl, autoFocusFound, startPos;
|
||||
var self = this, startPos;
|
||||
|
||||
setTimeout(function() {
|
||||
self.addClass('in');
|
||||
}, 0);
|
||||
|
||||
self.keyboardNavigation = new KeyboardNavigation({
|
||||
root: self,
|
||||
enableLeftRight: false,
|
||||
enableUpDown: false,
|
||||
items: items,
|
||||
onCancel: function() {
|
||||
self.close();
|
||||
}
|
||||
});
|
||||
|
||||
self.find('*').each(function(ctrl) {
|
||||
if (ctrl.canFocus) {
|
||||
autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
|
||||
focusCtrl = focusCtrl || ctrl;
|
||||
|
||||
// TODO: Figure out a better way
|
||||
if (ctrl.type == 'filepicker') {
|
||||
items.push(ctrl.getEl('inp'));
|
||||
|
||||
if (ctrl.getEl('open')) {
|
||||
items.push(ctrl.getEl('open'));
|
||||
}
|
||||
} else {
|
||||
items.push(ctrl.getEl());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (self.statusbar) {
|
||||
self.statusbar.find('*').each(function(ctrl) {
|
||||
if (ctrl.canFocus) {
|
||||
autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
|
||||
focusCtrl = focusCtrl || ctrl;
|
||||
items.push(ctrl.getEl());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self._super();
|
||||
|
||||
if (self.statusbar) {
|
||||
self.statusbar.postRender();
|
||||
}
|
||||
|
||||
if (!autoFocusFound && focusCtrl) {
|
||||
focusCtrl.focus();
|
||||
}
|
||||
self.focus();
|
||||
|
||||
this.dragHelper = new DragHelper(self._id + '-dragh', {
|
||||
start: function() {
|
||||
@@ -387,7 +353,7 @@ define("tinymce/ui/Window", [
|
||||
* @return {tinymce.ui.Control} Current control instance.
|
||||
*/
|
||||
remove: function() {
|
||||
var self = this;
|
||||
var self = this, prefix = self.classPrefix;
|
||||
|
||||
self.dragHelper.destroy();
|
||||
self._super();
|
||||
@@ -395,6 +361,22 @@ define("tinymce/ui/Window", [
|
||||
if (self.statusbar) {
|
||||
this.statusbar.remove();
|
||||
}
|
||||
|
||||
if (self._fullscreen) {
|
||||
DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
|
||||
DomUtils.removeClass(document.body, prefix + 'fullscreen');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the contentWindow object of the iframe if it exists.
|
||||
*
|
||||
* @method getContentWindow
|
||||
* @return {Window} window object or null.
|
||||
*/
|
||||
getContentWindow: function() {
|
||||
var ifr = this.getEl().getElementsByTagName('iframe')[0];
|
||||
return ifr ? ifr.contentWindow : null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Regular → Executable
+6
-6
@@ -27,16 +27,14 @@ define("tinymce/util/Class", [
|
||||
|
||||
// Provides classical inheritance, based on code made by John Resig
|
||||
Class.extend = extendClass = function(prop) {
|
||||
var Self = this, _super = Self.prototype, prototype, name, member;
|
||||
var self = this, _super = self.prototype, prototype, name, member;
|
||||
|
||||
// The dummy class constructor
|
||||
function Class() {
|
||||
var i, mixins, mixin, self;
|
||||
var i, mixins, mixin, self = this;
|
||||
|
||||
// All construction is actually done in the init method
|
||||
if (!initializing) {
|
||||
self = this;
|
||||
|
||||
// Run class constuctor
|
||||
if (self.init) {
|
||||
self.init.apply(self, arguments);
|
||||
@@ -64,7 +62,7 @@ define("tinymce/util/Class", [
|
||||
// Creates a overloaded method for the class
|
||||
// this enables you to use this._super(); to call the super function
|
||||
function createMethod(name, fn) {
|
||||
return function(){
|
||||
return function() {
|
||||
var self = this, tmp = self._super, ret;
|
||||
|
||||
self._super = _super[name];
|
||||
@@ -78,7 +76,9 @@ define("tinymce/util/Class", [
|
||||
// Instantiate a base class (but only create the instance,
|
||||
// don't run the init constructor)
|
||||
initializing = true;
|
||||
prototype = new Self();
|
||||
|
||||
/*eslint new-cap:0 */
|
||||
prototype = new self();
|
||||
initializing = false;
|
||||
|
||||
// Add mixins
|
||||
|
||||
Executable
+235
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Color.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class lets you parse/serialize colors and convert rgb/hsb.
|
||||
*
|
||||
* @class tinymce.util.Color
|
||||
* @example
|
||||
* var white = new tinymce.util.Color({r: 255, g: 255, b: 255});
|
||||
* var red = new tinymce.util.Color('#FF0000');
|
||||
*
|
||||
* console.log(white.toHex(), red.toHsv());
|
||||
*/
|
||||
define("tinymce/util/Color", [], function() {
|
||||
var min = Math.min, max = Math.max, round = Math.round;
|
||||
|
||||
/**
|
||||
* Constructs a new color instance.
|
||||
*
|
||||
* @constructor
|
||||
* @method Color
|
||||
* @param {String} value Optional initial value to parse.
|
||||
*/
|
||||
function Color(value) {
|
||||
var self = this, r = 0, g = 0, b = 0;
|
||||
|
||||
function rgb2hsv(r, g, b) {
|
||||
var h, s, v, d, minRGB, maxRGB;
|
||||
|
||||
h = 0;
|
||||
s = 0;
|
||||
v = 0;
|
||||
r = r / 255;
|
||||
g = g / 255;
|
||||
b = b / 255;
|
||||
|
||||
minRGB = min(r, min(g, b));
|
||||
maxRGB = max(r, max(g, b));
|
||||
|
||||
if (minRGB == maxRGB) {
|
||||
v = minRGB;
|
||||
|
||||
return {
|
||||
h: 0,
|
||||
s: 0,
|
||||
v: v * 100
|
||||
};
|
||||
}
|
||||
|
||||
/*eslint no-nested-ternary:0 */
|
||||
d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r);
|
||||
h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5);
|
||||
h = 60 * (h - d / (maxRGB - minRGB));
|
||||
s = (maxRGB - minRGB) / maxRGB;
|
||||
v = maxRGB;
|
||||
|
||||
return {
|
||||
h: round(h),
|
||||
s: round(s * 100),
|
||||
v: round(v * 100)
|
||||
};
|
||||
}
|
||||
|
||||
function hsvToRgb(hue, saturation, brightness) {
|
||||
var side, chroma, x, match;
|
||||
|
||||
hue = (parseInt(hue, 10) || 0) % 360;
|
||||
saturation = parseInt(saturation, 10) / 100;
|
||||
brightness = parseInt(brightness, 10) / 100;
|
||||
saturation = max(0, min(saturation, 1));
|
||||
brightness = max(0, min(brightness, 1));
|
||||
|
||||
if (saturation === 0) {
|
||||
r = g = b = round(255 * brightness);
|
||||
return;
|
||||
}
|
||||
|
||||
side = hue / 60;
|
||||
chroma = brightness * saturation;
|
||||
x = chroma * (1 - Math.abs(side % 2 - 1));
|
||||
match = brightness - chroma;
|
||||
|
||||
switch (Math.floor(side)) {
|
||||
case 0:
|
||||
r = chroma;
|
||||
g = x;
|
||||
b = 0;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
r = x;
|
||||
g = chroma;
|
||||
b = 0;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
r = 0;
|
||||
g = chroma;
|
||||
b = x;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
r = 0;
|
||||
g = x;
|
||||
b = chroma;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
r = x;
|
||||
g = 0;
|
||||
b = chroma;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
r = chroma;
|
||||
g = 0;
|
||||
b = x;
|
||||
break;
|
||||
|
||||
default:
|
||||
r = g = b = 0;
|
||||
}
|
||||
|
||||
r = round(255 * (r + match));
|
||||
g = round(255 * (g + match));
|
||||
b = round(255 * (b + match));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hex string of the current color. For example: #ff00ff
|
||||
*
|
||||
* @method toHex
|
||||
* @return {String} Hex string of current color.
|
||||
*/
|
||||
function toHex() {
|
||||
function hex(val) {
|
||||
val = parseInt(val, 10).toString(16);
|
||||
|
||||
return val.length > 1 ? val : '0' + val;
|
||||
}
|
||||
|
||||
return '#' + hex(r) + hex(g) + hex(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the r, g, b values of the color. Each channel has a range from 0-255.
|
||||
*
|
||||
* @method toRgb
|
||||
* @return {Object} Object with r, g, b fields.
|
||||
*/
|
||||
function toRgb() {
|
||||
return {
|
||||
r: r,
|
||||
g: g,
|
||||
b: b
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100.
|
||||
*
|
||||
* @method toHsv
|
||||
* @return {Object} Object with h, s, v fields.
|
||||
*/
|
||||
function toHsv() {
|
||||
return rgb2hsv(r, g, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the specified value and populates the color instance.
|
||||
*
|
||||
* Supported format examples:
|
||||
* * rbg(255,0,0)
|
||||
* * #ff0000
|
||||
* * #fff
|
||||
* * {r: 255, g: 0, b: 0}
|
||||
* * {h: 360, s: 100, v: 100}
|
||||
*
|
||||
* @method parse
|
||||
* @param {Object/String} value Color value to parse.
|
||||
* @return {tinymce.util.Color} Current color instance.
|
||||
*/
|
||||
function parse(value) {
|
||||
var matches;
|
||||
|
||||
if (typeof value == 'object') {
|
||||
if ("r" in value) {
|
||||
r = value.r;
|
||||
g = value.g;
|
||||
b = value.b;
|
||||
} else if ("v" in value) {
|
||||
hsvToRgb(value.h, value.s, value.v);
|
||||
}
|
||||
} else {
|
||||
if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) {
|
||||
r = parseInt(matches[1], 10);
|
||||
g = parseInt(matches[2], 10);
|
||||
b = parseInt(matches[3], 10);
|
||||
} else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) {
|
||||
r = parseInt(matches[1], 16);
|
||||
g = parseInt(matches[2], 16);
|
||||
b = parseInt(matches[3], 16);
|
||||
} else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) {
|
||||
r = parseInt(matches[1] + matches[1], 16);
|
||||
g = parseInt(matches[2] + matches[2], 16);
|
||||
b = parseInt(matches[3] + matches[3], 16);
|
||||
}
|
||||
}
|
||||
|
||||
r = r < 0 ? 0 : (r > 255 ? 255 : r);
|
||||
g = g < 0 ? 0 : (g > 255 ? 255 : g);
|
||||
b = b < 0 ? 0 : (b > 255 ? 255 : b);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
parse(value);
|
||||
}
|
||||
|
||||
self.toRgb = toRgb;
|
||||
self.toHsv = toHsv;
|
||||
self.toHex = toHex;
|
||||
self.parse = parse;
|
||||
}
|
||||
|
||||
return Color;
|
||||
});
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* EventDispatcher.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class lets you add/remove and fire events by name on the specified scope. This makes
|
||||
* it easy to add event listener logic to any class.
|
||||
*
|
||||
* @class tinymce.util.EventDispatcher
|
||||
* @example
|
||||
* var eventDispatcher = new EventDispatcher();
|
||||
*
|
||||
* eventDispatcher.on('click', function() {console.log('data');});
|
||||
* eventDispatcher.fire('click', {data: 123});
|
||||
*/
|
||||
define("tinymce/util/EventDispatcher", [
|
||||
"tinymce/util/Tools"
|
||||
], function(Tools) {
|
||||
var nativeEvents = Tools.makeMap(
|
||||
"focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
|
||||
"mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
|
||||
"draggesture dragdrop drop drag submit " +
|
||||
"compositionstart compositionend compositionupdate touchstart touchend",
|
||||
' '
|
||||
);
|
||||
|
||||
function Dispatcher(settings) {
|
||||
var self = this, scope, bindings = {}, toggleEvent;
|
||||
|
||||
function returnFalse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function returnTrue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
settings = settings || {};
|
||||
scope = settings.scope || self;
|
||||
toggleEvent = settings.toggleEvent || returnFalse;
|
||||
|
||||
/**
|
||||
* Fires the specified event by name.
|
||||
*
|
||||
* @method fire
|
||||
* @param {String} name Name of the event to fire.
|
||||
* @param {Object?} args Event arguments.
|
||||
* @return {Object} Event args instance passed in.
|
||||
* @example
|
||||
* instance.fire('event', {...});
|
||||
*/
|
||||
function fire(name, args) {
|
||||
var handlers, i, l, callback;
|
||||
|
||||
name = name.toLowerCase();
|
||||
args = args || {};
|
||||
args.type = name;
|
||||
|
||||
// Setup target is there isn't one
|
||||
if (!args.target) {
|
||||
args.target = scope;
|
||||
}
|
||||
|
||||
// Add event delegation methods if they are missing
|
||||
if (!args.preventDefault) {
|
||||
// Add preventDefault method
|
||||
args.preventDefault = function() {
|
||||
args.isDefaultPrevented = returnTrue;
|
||||
};
|
||||
|
||||
// Add stopPropagation
|
||||
args.stopPropagation = function() {
|
||||
args.isPropagationStopped = returnTrue;
|
||||
};
|
||||
|
||||
// Add stopImmediatePropagation
|
||||
args.stopImmediatePropagation = function() {
|
||||
args.isImmediatePropagationStopped = returnTrue;
|
||||
};
|
||||
|
||||
// Add event delegation states
|
||||
args.isDefaultPrevented = returnFalse;
|
||||
args.isPropagationStopped = returnFalse;
|
||||
args.isImmediatePropagationStopped = returnFalse;
|
||||
}
|
||||
|
||||
if (settings.beforeFire) {
|
||||
settings.beforeFire(args);
|
||||
}
|
||||
|
||||
handlers = bindings[name];
|
||||
if (handlers) {
|
||||
for (i = 0, l = handlers.length; i < l; i++) {
|
||||
callback = handlers[i];
|
||||
|
||||
// Unbind handlers marked with "once"
|
||||
if (callback.once) {
|
||||
off(name, callback.func);
|
||||
}
|
||||
|
||||
// Stop immediate propagation if needed
|
||||
if (args.isImmediatePropagationStopped()) {
|
||||
args.stopPropagation();
|
||||
return args;
|
||||
}
|
||||
|
||||
// If callback returns false then prevent default and stop all propagation
|
||||
if (callback.func.call(scope, args) === false) {
|
||||
args.preventDefault();
|
||||
return args;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds an event listener to a specific event by name.
|
||||
*
|
||||
* @method on
|
||||
* @param {String} name Event name or space separated list of events to bind.
|
||||
* @param {callback} callback Callback to be executed when the event occurs.
|
||||
* @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
|
||||
* @return {Object} Current class instance.
|
||||
* @example
|
||||
* instance.on('event', function(e) {
|
||||
* // Callback logic
|
||||
* });
|
||||
*/
|
||||
function on(name, callback, prepend, extra) {
|
||||
var handlers, names, i;
|
||||
|
||||
if (callback === false) {
|
||||
callback = returnFalse;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback = {
|
||||
func: callback
|
||||
};
|
||||
|
||||
if (extra) {
|
||||
Tools.extend(callback, extra);
|
||||
}
|
||||
|
||||
names = name.toLowerCase().split(' ');
|
||||
i = names.length;
|
||||
while (i--) {
|
||||
name = names[i];
|
||||
handlers = bindings[name];
|
||||
if (!handlers) {
|
||||
handlers = bindings[name] = [];
|
||||
toggleEvent(name, true);
|
||||
}
|
||||
|
||||
if (prepend) {
|
||||
handlers.unshift(callback);
|
||||
} else {
|
||||
handlers.push(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds an event listener to a specific event by name.
|
||||
*
|
||||
* @method off
|
||||
* @param {String?} name Name of the event to unbind.
|
||||
* @param {callback?} callback Callback to unbind.
|
||||
* @return {Object} Current class instance.
|
||||
* @example
|
||||
* // Unbind specific callback
|
||||
* instance.off('event', handler);
|
||||
*
|
||||
* // Unbind all listeners by name
|
||||
* instance.off('event');
|
||||
*
|
||||
* // Unbind all events
|
||||
* instance.off();
|
||||
*/
|
||||
function off(name, callback) {
|
||||
var i, handlers, bindingName, names, hi;
|
||||
|
||||
if (name) {
|
||||
names = name.toLowerCase().split(' ');
|
||||
i = names.length;
|
||||
while (i--) {
|
||||
name = names[i];
|
||||
handlers = bindings[name];
|
||||
|
||||
// Unbind all handlers
|
||||
if (!name) {
|
||||
for (bindingName in bindings) {
|
||||
toggleEvent(bindingName, false);
|
||||
delete bindings[bindingName];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
if (handlers) {
|
||||
// Unbind all by name
|
||||
if (!callback) {
|
||||
handlers.length = 0;
|
||||
} else {
|
||||
// Unbind specific ones
|
||||
hi = handlers.length;
|
||||
while (hi--) {
|
||||
if (handlers[hi].func === callback) {
|
||||
handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
|
||||
bindings[name] = handlers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!handlers.length) {
|
||||
toggleEvent(name, false);
|
||||
delete bindings[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (name in bindings) {
|
||||
toggleEvent(name, false);
|
||||
}
|
||||
|
||||
bindings = {};
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds an event listener to a specific event by name
|
||||
* and automatically unbind the event once the callback fires.
|
||||
*
|
||||
* @method once
|
||||
* @param {String} name Event name or space separated list of events to bind.
|
||||
* @param {callback} callback Callback to be executed when the event occurs.
|
||||
* @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
|
||||
* @return {Object} Current class instance.
|
||||
* @example
|
||||
* instance.once('event', function(e) {
|
||||
* // Callback logic
|
||||
* });
|
||||
*/
|
||||
function once(name, callback, prepend) {
|
||||
return on(name, callback, prepend, {once: true});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true/false if the dispatcher has a event of the specified name.
|
||||
*
|
||||
* @method has
|
||||
* @param {String} name Name of the event to check for.
|
||||
* @return {Boolean} true/false if the event exists or not.
|
||||
*/
|
||||
function has(name) {
|
||||
name = name.toLowerCase();
|
||||
return !(!bindings[name] || bindings[name].length === 0);
|
||||
}
|
||||
|
||||
// Expose
|
||||
self.fire = fire;
|
||||
self.on = on;
|
||||
self.off = off;
|
||||
self.once = once;
|
||||
self.has = has;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true/false if the specified event name is a native browser event or not.
|
||||
*
|
||||
* @method isNative
|
||||
* @param {String} name Name to check if it's native.
|
||||
* @return {Boolean} true/false if the event is native or not.
|
||||
* @static
|
||||
*/
|
||||
Dispatcher.isNative = function(name) {
|
||||
return !!nativeEvents[name.toLowerCase()];
|
||||
};
|
||||
|
||||
return Dispatcher;
|
||||
});
|
||||
Regular → Executable
+44
-9
@@ -17,14 +17,36 @@
|
||||
define("tinymce/util/I18n", [], function() {
|
||||
"use strict";
|
||||
|
||||
var data = {};
|
||||
var data = {}, code = "en";
|
||||
|
||||
return {
|
||||
/**
|
||||
* Sets the current language code.
|
||||
*
|
||||
* @method setCode
|
||||
* @param {String} newCode Current language code.
|
||||
*/
|
||||
setCode: function(newCode) {
|
||||
if (newCode) {
|
||||
code = newCode;
|
||||
this.rtl = this.data[newCode] ? this.data[newCode]._dir === 'rtl' : false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current language code.
|
||||
*
|
||||
* @return {String} Current language code.
|
||||
*/
|
||||
getCode: function() {
|
||||
return code;
|
||||
},
|
||||
|
||||
/**
|
||||
* Property gets set to true if a RTL language pack was loaded.
|
||||
*
|
||||
* @property rtl
|
||||
* @type {Boolean}
|
||||
* @type Boolean
|
||||
*/
|
||||
rtl: false,
|
||||
|
||||
@@ -36,11 +58,17 @@ define("tinymce/util/I18n", [], function() {
|
||||
* @param {Array} items Name/value array with English en_US to sv_SE.
|
||||
*/
|
||||
add: function(code, items) {
|
||||
for (var name in items) {
|
||||
data[name] = items[name];
|
||||
var langData = data[code];
|
||||
|
||||
if (!langData) {
|
||||
data[code] = langData = {};
|
||||
}
|
||||
|
||||
this.rtl = this.rtl || data._dir === 'rtl';
|
||||
for (var name in items) {
|
||||
langData[name] = items[name];
|
||||
}
|
||||
|
||||
this.setCode(code);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -56,23 +84,30 @@ define("tinymce/util/I18n", [], function() {
|
||||
* @return {String} String that got translated.
|
||||
*/
|
||||
translate: function(text) {
|
||||
if (typeof(text) == "undefined") {
|
||||
var langData;
|
||||
|
||||
langData = data[code];
|
||||
if (!langData) {
|
||||
langData = {};
|
||||
}
|
||||
|
||||
if (typeof text == "undefined") {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (typeof(text) != "string" && text.raw) {
|
||||
if (typeof text != "string" && text.raw) {
|
||||
return text.raw;
|
||||
}
|
||||
|
||||
if (text.push) {
|
||||
var values = text.slice(1);
|
||||
|
||||
text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) {
|
||||
text = (langData[text[0]] || text[0]).replace(/\{([0-9]+)\}/g, function(match1, match2) {
|
||||
return values[match2];
|
||||
});
|
||||
}
|
||||
|
||||
return data[text] || text;
|
||||
return (langData[text] || text).replace(/{context:\w+}$/, '');
|
||||
},
|
||||
|
||||
data: data
|
||||
|
||||
Loaded 100 of 1453 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user