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

No files matched your search

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