| | |
| | | var tinymce = {
|
| | | majorVersion : '3',
|
| | |
|
| | | minorVersion : '4.1',
|
| | | minorVersion : '4.6',
|
| | |
|
| | | releaseDate : '2011-03-24',
|
| | | releaseDate : '2011-09-29',
|
| | |
|
| | | _init : function() {
|
| | | var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
|
| | |
| | |
|
| | | t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
|
| | |
|
| | | t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
|
| | |
|
| | | t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
|
| | |
|
| | | t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
|
| | |
|
| | | t.isGecko = !t.isWebKit && /Gecko/.test(ua);
|
| | |
|
| | | t.isMac = ua.indexOf('Mac') != -1;
|
| | |
| | | t.isAir = /adobeair/i.test(ua);
|
| | |
|
| | | t.isIDevice = /(iPad|iPhone)/.test(ua);
|
| | | |
| | | t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
|
| | |
|
| | | // TinyMCE .NET webcontrol might be setting the values for TinyMCE
|
| | | if (win.tinyMCEPreInit) {
|
| | |
| | | })(window);
|
| | |
|
| | |
|
| | |
|
| | | tinymce.create('tinymce.util.Dispatcher', {
|
| | | scope : null,
|
| | | listeners : null,
|
| | |
| | |
|
| | | tinymce.create('tinymce.util.URI', {
|
| | | URI : function(u, s) {
|
| | | var t = this, o, a, b;
|
| | | var t = this, o, a, b, base_url;
|
| | |
|
| | | // Trim whitespace
|
| | | u = tinymce.trim(u);
|
| | |
| | | // Default settings
|
| | | s = t.settings = s || {};
|
| | |
|
| | | // Strange app protocol or local anchor
|
| | | if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
|
| | | // Strange app protocol that isn't http/https or local anchor
|
| | | // For example: mailto,skype,tel etc.
|
| | | if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
|
| | | t.source = u;
|
| | | return;
|
| | | }
|
| | |
| | | u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
|
| | |
|
| | | // Relative path http:// or protocol relative //path
|
| | | if (!/^\w*:?\/\//.test(u))
|
| | | u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
|
| | | if (!/^[\w-]*:?\/\//.test(u)) {
|
| | | base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
|
| | | u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
|
| | | }
|
| | |
|
| | | // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
|
| | | u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
|
| | |
| | | }
|
| | | });
|
| | | }());
|
| | | (function(tinymce){
|
| | | tinymce.VK = {
|
| | | DELETE:46,
|
| | | BACKSPACE:8
|
| | | |
| | | }
|
| | |
|
| | | })(tinymce);
|
| | |
|
| | | (function(tinymce) {
|
| | | var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE;
|
| | |
|
| | | function cleanupStylesWhenDeleting(ed) {
|
| | | var dom = ed.dom, selection = ed.selection;
|
| | |
|
| | | ed.onKeyDown.add(function(ed, e) {
|
| | | var rng, blockElm, node, clonedSpan, isDelete;
|
| | |
|
| | | isDelete = e.keyCode == DELETE;
|
| | | if (isDelete || e.keyCode == BACKSPACE) {
|
| | | e.preventDefault();
|
| | | rng = selection.getRng();
|
| | |
|
| | | // Find root block
|
| | | blockElm = dom.getParent(rng.startContainer, dom.isBlock);
|
| | |
|
| | | // On delete clone the root span of the next block element
|
| | | if (isDelete)
|
| | | blockElm = dom.getNext(blockElm, dom.isBlock);
|
| | |
|
| | | // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
|
| | | if (blockElm) {
|
| | | node = blockElm.firstChild;
|
| | |
|
| | | // Ignore empty text nodes
|
| | | while (node.nodeType == 3 && node.nodeValue.length == 0)
|
| | | node = node.nextSibling;
|
| | |
|
| | | if (node && node.nodeName === 'SPAN') {
|
| | | clonedSpan = node.cloneNode(false);
|
| | | }
|
| | | }
|
| | |
|
| | | // Do the backspace/delete actiopn
|
| | | ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
|
| | |
|
| | | // Find all odd apple-style-spans
|
| | | blockElm = dom.getParent(rng.startContainer, dom.isBlock);
|
| | | tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
|
| | | var bm = selection.getBookmark();
|
| | |
|
| | | if (clonedSpan) {
|
| | | dom.replace(clonedSpan.cloneNode(false), span, true);
|
| | | } else {
|
| | | dom.remove(span, true);
|
| | | }
|
| | |
|
| | | // Restore the selection
|
| | | selection.moveToBookmark(bm);
|
| | | });
|
| | | }
|
| | | });
|
| | | };
|
| | |
|
| | | function emptyEditorWhenDeleting(ed) {
|
| | | ed.onKeyUp.add(function(ed, e) {
|
| | | var keyCode = e.keyCode;
|
| | |
|
| | | if (keyCode == DELETE || keyCode == BACKSPACE) {
|
| | | if (ed.dom.isEmpty(ed.getBody())) {
|
| | | ed.setContent('', {format : 'raw'});
|
| | | ed.nodeChanged();
|
| | | return;
|
| | | }
|
| | | }
|
| | | });
|
| | | };
|
| | |
|
| | | function inputMethodFocus(ed) {
|
| | | ed.dom.bind(ed.getDoc(), 'focusin', function() {
|
| | | ed.selection.setRng(ed.selection.getRng());
|
| | | });
|
| | | };
|
| | |
|
| | | function focusBody(ed) {
|
| | | // Fix for a focus bug in FF 3.x where the body element
|
| | | // wouldn't get proper focus if the user clicked on the HTML element
|
| | | if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
|
| | | ed.onMouseDown.add(function(ed, e) {
|
| | | if (e.target.nodeName === "HTML") {
|
| | | var body = ed.getBody();
|
| | |
|
| | | // Blur the body it's focused but not correctly focused
|
| | | body.blur();
|
| | |
|
| | | // Refocus the body after a little while
|
| | | setTimeout(function() {
|
| | | body.focus();
|
| | | }, 0);
|
| | | }
|
| | | });
|
| | | }
|
| | | };
|
| | |
|
| | | function selectControlElements(ed) {
|
| | | ed.onClick.add(function(ed, e) {
|
| | | e = e.target;
|
| | |
|
| | | // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
|
| | | // WebKit can't even do simple things like selecting an image
|
| | | // Needs tobe the setBaseAndExtend or it will fail to select floated images
|
| | | if (/^(IMG|HR)$/.test(e.nodeName))
|
| | | ed.selection.getSel().setBaseAndExtent(e, 0, e, 1);
|
| | |
|
| | | if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor'))
|
| | | ed.selection.select(e);
|
| | |
|
| | | ed.nodeChanged();
|
| | | });
|
| | | };
|
| | |
|
| | | tinymce.create('tinymce.util.Quirks', {
|
| | | Quirks: function(ed) {
|
| | | // WebKit
|
| | | if (tinymce.isWebKit) {
|
| | | cleanupStylesWhenDeleting(ed);
|
| | | emptyEditorWhenDeleting(ed);
|
| | | inputMethodFocus(ed);
|
| | | selectControlElements(ed);
|
| | | }
|
| | |
|
| | | // IE
|
| | | if (tinymce.isIE) {
|
| | | emptyEditorWhenDeleting(ed);
|
| | | }
|
| | |
|
| | | // Gecko
|
| | | if (tinymce.isGecko) {
|
| | | focusBody(ed);
|
| | | }
|
| | | }
|
| | | });
|
| | | })(tinymce);
|
| | | (function(tinymce) {
|
| | | var namedEntities, baseEntities, reverseEntities,
|
| | | attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
| | | attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
| | | textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
| | | rawCharsRegExp = /[<>&\"\']/g,
|
| | | entityRegExp = /&(#)?([\w]+);/g,
|
| | | entityRegExp = /&(#x|#)?([\w]+);/g,
|
| | | asciiMap = {
|
| | | 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
|
| | | 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
|
| | |
| | |
|
| | | // Raw entities
|
| | | baseEntities = {
|
| | | '"' : '"',
|
| | | '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
|
| | | "'" : ''',
|
| | | '<' : '<',
|
| | | '>' : '>',
|
| | |
| | | decode : function(text) {
|
| | | return text.replace(entityRegExp, function(all, numeric, value) {
|
| | | if (numeric) {
|
| | | value = parseInt(value);
|
| | | value = parseInt(value, numeric.length === 2 ? 16 : 10);
|
| | |
|
| | | // Support upper UTF
|
| | | if (value > 0xFFFF) {
|
| | |
| | |
|
| | | settings = settings || {};
|
| | |
|
| | | encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
|
| | | encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
|
| | | for (i = 0; i < encodingItems.length; i++) {
|
| | | encodingLookup[encodingItems[i]] = '_' + i;
|
| | | encodingLookup['_' + i] = encodingItems[i];
|
| | | encodingLookup[encodingItems[i]] = '\uFEFF' + i;
|
| | | encodingLookup['\uFEFF' + i] = encodingItems[i];
|
| | | }
|
| | |
|
| | | function toHex(match, r, g, b) {
|
| | |
| | | // It will also decode the \" \' if keep_slashes is set to fale or omitted
|
| | | function decode(str, keep_slashes) {
|
| | | if (isEncoded) {
|
| | | str = str.replace(/_[0-9]/g, function(str) {
|
| | | str = str.replace(/\uFEFF[0-9]/g, function(str) {
|
| | | return encodingLookup[str];
|
| | | });
|
| | | }
|
| | |
| | |
|
| | | if (css) {
|
| | | // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
|
| | | css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
|
| | | css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
|
| | | return str.replace(/[;:]/g, encode);
|
| | | });
|
| | |
|
| | |
| | | var css = '', name, value;
|
| | |
|
| | | function serializeStyles(name) {
|
| | | var styleList, i, l, name, value;
|
| | | var styleList, i, l, value;
|
| | |
|
| | | styleList = schema.styles[name];
|
| | | if (styleList) {
|
| | |
| | | name = styleList[i];
|
| | | value = styles[name];
|
| | |
|
| | | if (value !== undef)
|
| | | if (value !== undef && value.length > 0)
|
| | | css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
|
| | | }
|
| | | }
|
| | |
| | | if (element_name && schema && schema.styles) {
|
| | | // Serialize global styles and element specific styles
|
| | | serializeStyles('*');
|
| | | serializeStyles(name);
|
| | | serializeStyles(element_name);
|
| | | } else {
|
| | | // Output the styles in the order they are inside the object
|
| | | for (name in styles) {
|
| | | value = styles[name];
|
| | |
|
| | | if (value !== undef)
|
| | | if (value !== undef && value.length > 0)
|
| | | css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
|
| | | }
|
| | | }
|
| | |
| | | };
|
| | |
|
| | | (function(tinymce) {
|
| | | var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
|
| | | whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
|
| | | var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},
|
| | | defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
|
| | |
|
| | | function split(str, delim) {
|
| | | return str.split(delim || ',');
|
| | |
| | | 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
|
| | | );
|
| | |
|
| | | boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
|
| | | boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls');
|
| | | shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
|
| | | nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
|
| | | whiteSpaceElementsMap = makeMap('pre,script,style');
|
| | | nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap);
|
| | | defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea');
|
| | | selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
|
| | |
|
| | | tinymce.html.Schema = function(settings) {
|
| | | var self = this, elements = {}, children = {}, patternElements = [], validStyles;
|
| | | var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap;
|
| | |
|
| | | settings = settings || {};
|
| | |
|
| | |
| | | validStyles[key] = tinymce.explode(value);
|
| | | });
|
| | | }
|
| | |
|
| | | whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap;
|
| | |
|
| | | // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
|
| | | function patternToRegExp(str) {
|
| | |
| | | if (custom_elements) {
|
| | | each(split(custom_elements), function(rule) {
|
| | | var matches = customElementRegExp.exec(rule),
|
| | | cloneName = matches[1] === '~' ? 'span' : 'div',
|
| | | inline = matches[1] === '~',
|
| | | cloneName = inline ? 'span' : 'div',
|
| | | name = matches[2];
|
| | |
|
| | | children[name] = children[cloneName];
|
| | | customElementsMap[name] = cloneName;
|
| | |
|
| | | // If it's not marked as inline then add it to valid block elements
|
| | | if (!inline)
|
| | | blockElementsMap[name] = {};
|
| | |
|
| | | // Add custom elements at span/div positions
|
| | | each(children, function(element, child) {
|
| | |
| | | }
|
| | | });
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | | function getElementRule(name) {
|
| | | var element = elements[name], i;
|
| | |
|
| | | // Exact match found
|
| | | if (element)
|
| | | return element;
|
| | |
|
| | | // No exact match then try the patterns
|
| | | i = patternElements.length;
|
| | | while (i--) {
|
| | | element = patternElements[i];
|
| | |
|
| | | if (element.pattern.test(name))
|
| | | return element;
|
| | | }
|
| | | };
|
| | |
|
| | | if (!settings.valid_elements) {
|
| | | // No valid elements defined then clone the elements from the transitional spec
|
| | |
| | | elements.img.attributesDefault = [{name: 'alt', value: ''}];
|
| | |
|
| | | // Remove these if they are empty by default
|
| | | each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
|
| | | each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {
|
| | | elements[name].removeEmpty = true;
|
| | | });
|
| | |
|
| | |
| | |
|
| | | // Todo: Remove this when we fix list handling to be valid
|
| | | addValidChildren('+ol[ul|ol],+ul[ul|ol]');
|
| | |
|
| | | // If the user didn't allow span only allow internal spans
|
| | | if (!getElementRule('span'))
|
| | | addValidElements('span[!data-mce-type|*]');
|
| | |
|
| | | // Delete invalid elements
|
| | | if (settings.invalid_elements) {
|
| | |
| | | return !!(parent && parent[child]);
|
| | | };
|
| | |
|
| | | self.getElementRule = function(name) {
|
| | | var element = elements[name], i;
|
| | | self.getElementRule = getElementRule;
|
| | |
|
| | | // Exact match found
|
| | | if (element)
|
| | | return element;
|
| | |
|
| | | // No exact match then try the patterns
|
| | | i = patternElements.length;
|
| | | while (i--) {
|
| | | element = patternElements[i];
|
| | |
|
| | | if (element.pattern.test(name))
|
| | | return element;
|
| | | }
|
| | | self.getCustomElements = function() {
|
| | | return customElementsMap;
|
| | | };
|
| | |
|
| | | self.addValidElements = addValidElements;
|
| | |
| | | });
|
| | |
|
| | | self.parse = function(html) {
|
| | | var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
|
| | | shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
|
| | | var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
|
| | | shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
|
| | | validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
|
| | | tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
|
| | | tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
|
| | |
|
| | | function processEndTag(name) {
|
| | | var pos, i;
|
| | |
| | | selfClosing = schema.getSelfClosingElements();
|
| | | fillAttrsMap = schema.getBoolAttrs();
|
| | | validate = settings.validate;
|
| | | removeInternalElements = settings.remove_internals;
|
| | | fixSelfClosing = settings.fix_self_closing;
|
| | | isIE = tinymce.isIE;
|
| | | invalidPrefixRegExp = /^:/;
|
| | |
|
| | | while (matches = tokenRegExp.exec(html)) {
|
| | | // Text
|
| | |
| | | self.text(decode(html.substr(index, matches.index - index)));
|
| | |
|
| | | if (value = matches[6]) { // End element
|
| | | processEndTag(value.toLowerCase());
|
| | | value = value.toLowerCase();
|
| | |
|
| | | // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
|
| | | if (isIE && invalidPrefixRegExp.test(value))
|
| | | value = value.substr(1);
|
| | |
|
| | | processEndTag(value);
|
| | | } else if (value = matches[7]) { // Start element
|
| | | value = value.toLowerCase();
|
| | |
|
| | | // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
|
| | | if (isIE && invalidPrefixRegExp.test(value))
|
| | | value = value.substr(1);
|
| | |
|
| | | isShortEnded = value in shortEndedElements;
|
| | |
|
| | | // Is self closing tag for example an <li> after an open <li>
|
| | |
| | |
|
| | | // Parse attributes
|
| | | if (attribsValue = matches[8]) {
|
| | | isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
|
| | |
|
| | | // If the element has internal attributes then remove it if we are told to do so
|
| | | if (isInternalElement && removeInternalElements)
|
| | | isValidElement = false;
|
| | |
|
| | | attrList = [];
|
| | | attrList.map = {};
|
| | |
|
| | |
| | | value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
|
| | |
|
| | | // Validate name and value
|
| | | if (validate && name.indexOf('data-') !== 0) {
|
| | | if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
|
| | | attrRule = validAttributesMap[name];
|
| | |
|
| | | // Find rule by pattern matching
|
| | |
| | | }
|
| | |
|
| | | // Process attributes if validation is enabled
|
| | | if (validate) {
|
| | | if (validate && !isInternalElement) {
|
| | | attributesRequired = elementRule.attributesRequired;
|
| | | attributesDefault = elementRule.attributesDefault;
|
| | | attributesForced = elementRule.attributesForced;
|
| | |
| | | },
|
| | |
|
| | | isEmpty : function(elements) {
|
| | | var self = this, node = self.firstChild, i;
|
| | | var self = this, node = self.firstChild, i, name;
|
| | |
|
| | | if (node) {
|
| | | do {
|
| | | if (node.type === 1) {
|
| | | // Ignore bogus elements
|
| | | if (node.attributes.map['data-mce-bogus'])
|
| | | continue;
|
| | |
|
| | |
| | | if (elements[node.name])
|
| | | return false;
|
| | |
|
| | | // Keep elements with data attributes
|
| | | // Keep elements with data attributes or name attribute like <a name="1"></a>
|
| | | i = node.attributes.length;
|
| | | while (i--) {
|
| | | if (node.attributes[i].name.indexOf('data-') === 0)
|
| | | name = node.attributes[i].name;
|
| | | if (name === "name" || name.indexOf('data-') === 0)
|
| | | return false;
|
| | | }
|
| | | }
|
| | |
| | | }
|
| | |
|
| | | return true;
|
| | | },
|
| | |
|
| | | walk : function(prev) {
|
| | | return walk(this, null, prev);
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
|
| | |
|
| | | settings = settings || {};
|
| | | settings.validate = "validate" in settings ? settings.validate : true;
|
| | | settings.root_name = settings.root_name || 'body';
|
| | | self.schema = schema = schema || new tinymce.html.Schema();
|
| | |
|
| | |
| | | };
|
| | |
|
| | | self.parse = function(html, args) {
|
| | | var parser, rootNode, node, nodes, i, l, fi, fl, list, name,
|
| | | var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
|
| | | blockElements, startWhiteSpaceRegExp, invalidChildren = [],
|
| | | endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
|
| | | endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
|
| | |
|
| | | args = args || {};
|
| | | matchedNodes = {};
|
| | | matchedAttributes = {};
|
| | | blockElements = tinymce.extend(tinymce.makeMap('script,style,head,title,meta,param'), schema.getBlockElements());
|
| | | blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
|
| | | nonEmptyElements = schema.getNonEmptyElements();
|
| | | children = schema.children;
|
| | | validate = settings.validate;
|
| | | rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
|
| | |
|
| | | whiteSpaceElements = schema.getWhiteSpaceElements();
|
| | | startWhiteSpaceRegExp = /^[ \t\r\n]+/;
|
| | | endWhiteSpaceRegExp = /[ \t\r\n]+$/;
|
| | | allWhiteSpaceRegExp = /[ \t\r\n]+/g;
|
| | |
|
| | | function addRootBlocks() {
|
| | | var node = rootNode.firstChild, next, rootBlockNode;
|
| | |
|
| | | while (node) {
|
| | | next = node.next;
|
| | |
|
| | | if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
|
| | | if (!rootBlockNode) {
|
| | | // Create a new root block element
|
| | | rootBlockNode = createNode(rootBlockName, 1);
|
| | | rootNode.insert(rootBlockNode, node);
|
| | | rootBlockNode.append(node);
|
| | | } else
|
| | | rootBlockNode.append(node);
|
| | | } else {
|
| | | rootBlockNode = null;
|
| | | }
|
| | |
|
| | | node = next;
|
| | | };
|
| | | };
|
| | |
|
| | | function createNode(name, type) {
|
| | | var node = new Node(name, type), list;
|
| | |
| | | return node;
|
| | | };
|
| | |
|
| | | function removeWhitespaceBefore(node) {
|
| | | var textNode, textVal, sibling;
|
| | |
|
| | | for (textNode = node.prev; textNode && textNode.type === 3; ) {
|
| | | textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
|
| | |
|
| | | if (textVal.length > 0) {
|
| | | textNode.value = textVal;
|
| | | textNode = textNode.prev;
|
| | | } else {
|
| | | sibling = textNode.prev;
|
| | | textNode.remove();
|
| | | textNode = sibling;
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | | parser = new tinymce.html.SaxParser({
|
| | | validate : settings.validate,
|
| | | fix_self_closing : false, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
|
| | | validate : validate,
|
| | | fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
|
| | |
|
| | | cdata: function(text) {
|
| | | node.append(createNode('#cdata', 4)).value = text;
|
| | |
| | |
|
| | | pi: function(name, text) {
|
| | | node.append(createNode(name, 7)).value = text;
|
| | | removeWhitespaceBefore(node);
|
| | | },
|
| | |
|
| | | doctype: function(text) {
|
| | | node.append(createNode('#doctype', 10)).value = text;
|
| | | var newNode;
|
| | | |
| | | newNode = node.append(createNode('#doctype', 10));
|
| | | newNode.value = text;
|
| | | removeWhitespaceBefore(node);
|
| | | },
|
| | |
|
| | | start: function(name, attrs, empty) {
|
| | | var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
|
| | |
|
| | | elementRule = schema.getElementRule(name);
|
| | | elementRule = validate ? schema.getElementRule(name) : {};
|
| | | if (elementRule) {
|
| | | newNode = createNode(elementRule.outputName || name, 1);
|
| | | newNode.attributes = attrs;
|
| | |
| | | }
|
| | |
|
| | | // Trim whitespace before block
|
| | | if (blockElements[name]) {
|
| | | for (textNode = newNode.prev; textNode && textNode.type === 3; ) {
|
| | | text = textNode.value.replace(endWhiteSpaceRegExp, '');
|
| | |
|
| | | if (text.length > 0) {
|
| | | textNode.value = text;
|
| | | textNode = textNode.prev;
|
| | | } else {
|
| | | sibling = textNode.prev;
|
| | | textNode.remove();
|
| | | textNode = sibling;
|
| | | }
|
| | | }
|
| | | }
|
| | | if (blockElements[name])
|
| | | removeWhitespaceBefore(newNode);
|
| | |
|
| | | // Change current node if the element wasn't empty i.e not <br /> or <img />
|
| | | if (!empty)
|
| | |
| | | end: function(name) {
|
| | | var textNode, elementRule, text, sibling, tempNode;
|
| | |
|
| | | elementRule = schema.getElementRule(name);
|
| | | elementRule = validate ? schema.getElementRule(name) : {};
|
| | | if (elementRule) {
|
| | | if (blockElements[name]) {
|
| | | if (!whiteSpaceElements[node.name]) {
|
| | |
| | | }
|
| | | }, schema);
|
| | |
|
| | | rootNode = node = new Node(settings.root_name, 11);
|
| | | rootNode = node = new Node(args.context || settings.root_name, 11);
|
| | |
|
| | | parser.parse(html);
|
| | |
|
| | | fixInvalidChildren(invalidChildren);
|
| | |
|
| | | // Run node filters
|
| | | for (name in matchedNodes) {
|
| | | list = nodeFilters[name];
|
| | | nodes = matchedNodes[name];
|
| | |
|
| | | // Remove already removed children
|
| | | fi = nodes.length;
|
| | | while (fi--) {
|
| | | if (!nodes[fi].parent)
|
| | | nodes.splice(fi, 1);
|
| | | }
|
| | |
|
| | | for (i = 0, l = list.length; i < l; i++)
|
| | | list[i](nodes, name, args);
|
| | | // Fix invalid children or report invalid children in a contextual parsing
|
| | | if (validate && invalidChildren.length) {
|
| | | if (!args.context)
|
| | | fixInvalidChildren(invalidChildren);
|
| | | else
|
| | | args.invalid = true;
|
| | | }
|
| | |
|
| | | // Run attribute filters
|
| | | for (i = 0, l = attributeFilters.length; i < l; i++) {
|
| | | list = attributeFilters[i];
|
| | | // Wrap nodes in the root into block elements if the root is body
|
| | | if (rootBlockName && rootNode.name == 'body')
|
| | | addRootBlocks();
|
| | |
|
| | | if (list.name in matchedAttributes) {
|
| | | nodes = matchedAttributes[list.name];
|
| | | // Run filters only when the contents is valid
|
| | | if (!args.invalid) {
|
| | | // Run node filters
|
| | | for (name in matchedNodes) {
|
| | | list = nodeFilters[name];
|
| | | nodes = matchedNodes[name];
|
| | |
|
| | | // Remove already removed children
|
| | | fi = nodes.length;
|
| | |
| | | nodes.splice(fi, 1);
|
| | | }
|
| | |
|
| | | for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
|
| | | list.callbacks[fi](nodes, list.name, args);
|
| | | for (i = 0, l = list.length; i < l; i++)
|
| | | list[i](nodes, name, args);
|
| | | }
|
| | |
|
| | | // Run attribute filters
|
| | | for (i = 0, l = attributeFilters.length; i < l; i++) {
|
| | | list = attributeFilters[i];
|
| | |
|
| | | if (list.name in matchedAttributes) {
|
| | | nodes = matchedAttributes[list.name];
|
| | |
|
| | | // Remove already removed children
|
| | | fi = nodes.length;
|
| | | while (fi--) {
|
| | | if (!nodes[fi].parent)
|
| | | nodes.splice(fi, 1);
|
| | | }
|
| | |
|
| | | for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
|
| | | list.callbacks[fi](nodes, list.name, args);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | self.addNodeFilter('br', function(nodes, name) {
|
| | | var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
|
| | | nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
|
| | |
|
| | | // Remove brs from body element as well
|
| | | blockElements.body = 1;
|
| | |
|
| | | // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
|
| | | for (i = 0; i < l; i++) {
|
| | |
| | | elementRule = schema.getElementRule(parent.name);
|
| | |
|
| | | // Remove or padd the element depending on schema rule
|
| | | if (elementRule.removeEmpty)
|
| | | parent.remove();
|
| | | else if (elementRule.paddEmpty) |
| | | parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
|
| | | }
|
| | | if (elementRule) {
|
| | | if (elementRule.removeEmpty)
|
| | | parent.remove();
|
| | | else if (elementRule.paddEmpty)
|
| | | parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | else
|
| | | html[html.length] = ' />';
|
| | |
|
| | | /*if (indent && indentAfter[name])
|
| | | html.push('\n');*/
|
| | | if (empty && indent && indentAfter[name] && html.length > 0) {
|
| | | value = html[html.length - 1];
|
| | |
|
| | | if (value.length > 0 && value !== '\n')
|
| | | html.push('\n');
|
| | | }
|
| | | },
|
| | |
|
| | | end: function(name) {
|
| | |
| | | html.push('<?', name, ' ', text, '?>');
|
| | | else
|
| | | html.push('<?', name, '?>');
|
| | |
|
| | | if (indent)
|
| | | html.push('\n');
|
| | | },
|
| | |
|
| | | doctype: function(text) {
|
| | | html.push('<!DOCTYPE', text, '>');
|
| | | html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
|
| | | },
|
| | |
|
| | | reset: function() {
|
| | |
| | | isIE = tinymce.isIE,
|
| | | Entities = tinymce.html.Entities,
|
| | | simpleSelectorRe = /^([a-z0-9],?)+$/i,
|
| | | blockElementsMap = tinymce.html.Schema.blockElementsMap;
|
| | | blockElementsMap = tinymce.html.Schema.blockElementsMap,
|
| | | whiteSpaceRegExp = /^[ \t\r\n]*$/;
|
| | |
|
| | | tinymce.create('tinymce.dom.DOMUtils', {
|
| | | doc : null,
|
| | |
| | | },
|
| | |
|
| | | DOMUtils : function(d, s) {
|
| | | var t = this, globalStyle;
|
| | | var t = this, globalStyle, name;
|
| | |
|
| | | t.doc = d;
|
| | | t.win = window;
|
| | |
| | | keep_values : false,
|
| | | hex_colors : 1
|
| | | }, s);
|
| | |
|
| | | |
| | | t.schema = s.schema;
|
| | | t.styles = new tinymce.html.Styles({
|
| | | url_converter : s.url_converter,
|
| | | url_converter_scope : s.url_converter_scope
|
| | |
| | | }
|
| | | }
|
| | |
|
| | | if (isIE) {
|
| | | if (isIE && s.schema) {
|
| | | // Add missing HTML 4/5 elements to IE
|
| | | ('abbr article aside audio canvas ' +
|
| | | 'details figcaption figure footer ' +
|
| | |
| | | 'time video').replace(/\w+/g, function(name) {
|
| | | d.createElement(name);
|
| | | });
|
| | |
|
| | | // Create all custom elements
|
| | | for (name in s.schema.getCustomElements()) {
|
| | | d.createElement(name);
|
| | | }
|
| | | }
|
| | |
|
| | | tinymce.addUnload(t.destroy, t);
|
| | |
| | |
|
| | | remove : function(node, keep_children) {
|
| | | return this.run(node, function(node) {
|
| | | var parent, child;
|
| | |
|
| | | parent = node.parentNode;
|
| | | var child, parent = node.parentNode;
|
| | |
|
| | | if (!parent)
|
| | | return null;
|
| | |
| | | n = this.get(n);
|
| | |
|
| | | if (!n)
|
| | | return false;
|
| | | return;
|
| | |
|
| | | // Gecko
|
| | | if (this.doc.defaultView && c) {
|
| | |
| | | if (n.currentStyle && c)
|
| | | return n.currentStyle[na];
|
| | |
|
| | | return n.style[na];
|
| | | return n.style ? n.style[na] : undefined;
|
| | | },
|
| | |
|
| | | setStyles : function(e, o) {
|
| | |
| | |
|
| | | removeAllAttribs: function(e) {
|
| | | return this.run(e, function(e) {
|
| | | var attrs = e.attributes;
|
| | | for (var i = attrs.length - 1; i >= 0; i--) {
|
| | | var i, attrs = e.attributes;
|
| | | for (i = attrs.length - 1; i >= 0; i--) {
|
| | | e.removeAttributeNode(attrs.item(i));
|
| | | }
|
| | | });
|
| | |
| | | }
|
| | |
|
| | | break;
|
| | | |
| | |
|
| | | case "shape":
|
| | | e.setAttribute('data-mce-style', v);
|
| | | break;
|
| | |
| | | },
|
| | |
|
| | | getAttrib : function(e, n, dv) {
|
| | | var v, t = this;
|
| | | var v, t = this, undef;
|
| | |
|
| | | e = t.get(e);
|
| | |
|
| | | if (!e || e.nodeType !== 1)
|
| | | return false;
|
| | | return dv === undef ? false : dv;
|
| | |
|
| | | if (!is(dv))
|
| | | dv = '';
|
| | |
| | | }
|
| | | }
|
| | |
|
| | | return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
|
| | | return (v !== undef && v !== null && v !== '') ? '' + v : dv;
|
| | | },
|
| | |
|
| | | getPos : function(n, ro) {
|
| | |
| | | ro = ro || d.body;
|
| | |
|
| | | if (n) {
|
| | | // Use getBoundingClientRect on IE, Opera has it but it's not perfect
|
| | | if (isIE && !t.stdMode) {
|
| | | // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
|
| | | if (n.getBoundingClientRect) {
|
| | | n = n.getBoundingClientRect();
|
| | | e = t.boxModel ? d.documentElement : d.body;
|
| | | x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
|
| | | x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
|
| | |
|
| | | return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
|
| | | // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
|
| | | // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
|
| | | x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
|
| | | y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
|
| | |
|
| | | return {x : x, y : y};
|
| | | }
|
| | |
|
| | | r = n;
|
| | |
| | | return n.attributes;
|
| | | },
|
| | |
|
| | | isEmpty : function(node, elements) {
|
| | | var self = this, i, attributes, type, walker, name, parentNode;
|
| | |
|
| | | node = node.firstChild;
|
| | | if (node) {
|
| | | walker = new tinymce.dom.TreeWalker(node);
|
| | | elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
|
| | |
|
| | | do {
|
| | | type = node.nodeType;
|
| | |
|
| | | if (type === 1) {
|
| | | // Ignore bogus elements
|
| | | if (node.getAttribute('data-mce-bogus'))
|
| | | continue;
|
| | |
|
| | | // Keep empty elements like <img />
|
| | | name = node.nodeName.toLowerCase();
|
| | | if (elements && elements[name]) {
|
| | | // Ignore single BR elements in blocks like <p><br /></p>
|
| | | parentNode = node.parentNode;
|
| | | if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) {
|
| | | continue;
|
| | | }
|
| | |
|
| | | return false;
|
| | | }
|
| | |
|
| | | // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
|
| | | attributes = self.getAttribs(node);
|
| | | i = node.attributes.length;
|
| | | while (i--) {
|
| | | name = node.attributes[i].nodeName;
|
| | | if (name === "name" || name === 'data-mce-bookmark')
|
| | | return false;
|
| | | }
|
| | | }
|
| | |
|
| | | // Keep non whitespace text nodes
|
| | | if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
|
| | | return false;
|
| | | } while (node = walker.next());
|
| | | }
|
| | |
|
| | | return true;
|
| | | },
|
| | |
|
| | | destroy : function(s) {
|
| | | var t = this;
|
| | |
|
| | |
| | | },
|
| | |
|
| | | nodeIndex : function(node, normalized) {
|
| | | var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
|
| | | var idx = 0, lastNodeType, lastNode, nodeType;
|
| | |
|
| | | if (node) {
|
| | | for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
|
| | |
| | |
|
| | | // Normalize text nodes
|
| | | if (normalized && nodeType == 3) {
|
| | | // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
|
| | | // (the nodeValue attribute will not exist, and will error here).
|
| | | nodeValueExists = false;
|
| | | try {nodeValueExists = node.nodeValue.length} catch (c) {}
|
| | | if (nodeType == lastNodeType || !nodeValueExists)
|
| | | if (nodeType == lastNodeType || !node.nodeValue.length)
|
| | | continue;
|
| | | }
|
| | | idx++;
|
| | |
| | | // this function will then trim of empty edges and produce:
|
| | | // <p>text 1</p><b>CHOP</b><p>text 2</p>
|
| | | function trim(node) {
|
| | | var i, children = node.childNodes;
|
| | | var i, children = node.childNodes, type = node.nodeType;
|
| | |
|
| | | if (node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark')
|
| | | if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
|
| | | return;
|
| | |
|
| | | for (i = children.length - 1; i >= 0; i--)
|
| | | trim(children[i]);
|
| | |
|
| | | if (node.nodeType != 9) {
|
| | | if (type != 9) {
|
| | | // Keep non whitespace text nodes
|
| | | if (node.nodeType == 3 && node.nodeValue.length > 0) {
|
| | | if (type == 3 && node.nodeValue.length > 0) {
|
| | | // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
|
| | | if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
|
| | | return;
|
| | | }
|
| | |
|
| | | if (node.nodeType == 1) {
|
| | | } else if (type == 1) {
|
| | | // If the only child is a bookmark then move it up
|
| | | children = node.childNodes;
|
| | | if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
|
| | |
| | |
|
| | | (function() {
|
| | | function Selection(selection) {
|
| | | var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
|
| | | var self = this, dom = selection.dom, TRUE = true, FALSE = false;
|
| | |
|
| | | function getPosition(rng, start) {
|
| | | var checkRng, startIndex = 0, endIndex, inside,
|
| | | children, child, offset, index, position = -1, parent;
|
| | |
|
| | | // Setup test range, collapse it and get the parent
|
| | | checkRng = rng.duplicate();
|
| | | checkRng.collapse(start);
|
| | | parent = checkRng.parentElement();
|
| | |
|
| | | // Check if the selection is within the right document
|
| | | if (parent.ownerDocument !== selection.dom.doc)
|
| | | return;
|
| | |
|
| | | // IE will report non editable elements as it's parent so look for an editable one
|
| | | while (parent.contentEditable === "false") {
|
| | | parent = parent.parentNode;
|
| | | }
|
| | |
|
| | | // If parent doesn't have any children then return that we are inside the element
|
| | | if (!parent.hasChildNodes()) {
|
| | | return {node : parent, inside : 1};
|
| | | }
|
| | |
|
| | | // Setup node list and endIndex
|
| | | children = parent.children;
|
| | | endIndex = children.length - 1;
|
| | |
|
| | | // Perform a binary search for the position
|
| | | while (startIndex <= endIndex) {
|
| | | index = Math.floor((startIndex + endIndex) / 2);
|
| | |
|
| | | // Move selection to node and compare the ranges
|
| | | child = children[index];
|
| | | checkRng.moveToElementText(child);
|
| | | position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
|
| | |
|
| | | // Before/after or an exact match
|
| | | if (position > 0) {
|
| | | endIndex = index - 1;
|
| | | } else if (position < 0) {
|
| | | startIndex = index + 1;
|
| | | } else {
|
| | | return {node : child};
|
| | | }
|
| | | }
|
| | |
|
| | | // Check if child position is before or we didn't find a position
|
| | | if (position < 0) {
|
| | | // No element child was found use the parent element and the offset inside that
|
| | | if (!child) {
|
| | | checkRng.moveToElementText(parent);
|
| | | checkRng.collapse(true);
|
| | | child = parent;
|
| | | inside = true;
|
| | | } else
|
| | | checkRng.collapse(false);
|
| | |
|
| | | checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
|
| | |
|
| | | // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
|
| | | if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
|
| | | checkRng = rng.duplicate();
|
| | | checkRng.collapse(start);
|
| | |
|
| | | offset = -1;
|
| | | while (parent == checkRng.parentElement()) {
|
| | | if (checkRng.move('character', -1) == 0)
|
| | | break;
|
| | |
|
| | | offset++;
|
| | | }
|
| | | }
|
| | |
|
| | | offset = offset || checkRng.text.replace('\r\n', ' ').length;
|
| | | } else {
|
| | | // Child position is after the selection endpoint
|
| | | checkRng.collapse(true);
|
| | | checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
|
| | |
|
| | | // Get the length of the text to find where the endpoint is relative to it's container
|
| | | offset = checkRng.text.replace('\r\n', ' ').length;
|
| | | }
|
| | |
|
| | | return {node : child, position : position, offset : offset, inside : inside};
|
| | | };
|
| | |
|
| | | // Returns a W3C DOM compatible range object by using the IE Range API
|
| | | function getRange() {
|
| | | var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
|
| | | var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
|
| | |
|
| | | // If selection is outside the current document just return an empty range
|
| | | element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
|
| | |
| | |
|
| | | collapsed = selection.isCollapsed();
|
| | |
|
| | | // Handle control selection or text selection of a image
|
| | | if (ieRange.item || !element.hasChildNodes()) {
|
| | | if (collapsed) {
|
| | | domRange.setStart(element, 0);
|
| | | domRange.setEnd(element, 0);
|
| | | } else {
|
| | | domRange.setStart(element.parentNode, dom.nodeIndex(element));
|
| | | domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
|
| | | }
|
| | | // Handle control selection
|
| | | if (ieRange.item) {
|
| | | domRange.setStart(element.parentNode, dom.nodeIndex(element));
|
| | | domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
|
| | |
|
| | | return domRange;
|
| | | }
|
| | |
|
| | | function findEndPoint(start) {
|
| | | var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
|
| | | var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
|
| | |
|
| | | // Setup temp range and collapse it
|
| | | checkRng = ieRange.duplicate();
|
| | | checkRng.collapse(start);
|
| | | container = endPoint.node;
|
| | | offset = endPoint.offset;
|
| | |
|
| | | // Create marker and insert it at the end of the endpoints parent
|
| | | marker = dom.create('a');
|
| | | parent = checkRng.parentElement();
|
| | |
|
| | | // If parent doesn't have any children then set the container to that parent and the index to 0
|
| | | if (!parent.hasChildNodes()) {
|
| | | domRange[start ? 'setStart' : 'setEnd'](parent, 0);
|
| | | if (endPoint.inside && !container.hasChildNodes()) {
|
| | | domRange[start ? 'setStart' : 'setEnd'](container, 0);
|
| | | return;
|
| | | }
|
| | |
|
| | | parent.appendChild(marker);
|
| | | checkRng.moveToElementText(marker);
|
| | | position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
|
| | | if (position > 0) {
|
| | | // The position is after the end of the parent element.
|
| | | // This is the case where IE puts the caret to the left edge of a table.
|
| | | domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
|
| | | dom.remove(marker);
|
| | | if (offset === undef) {
|
| | | domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
|
| | | return;
|
| | | }
|
| | |
|
| | | // Setup node list and endIndex
|
| | | nodes = tinymce.grep(parent.childNodes);
|
| | | endIndex = nodes.length - 1;
|
| | | // Perform a binary search for the position
|
| | | while (startIndex <= endIndex) {
|
| | | index = Math.floor((startIndex + endIndex) / 2);
|
| | | if (endPoint.position < 0) {
|
| | | sibling = endPoint.inside ? container.firstChild : container.nextSibling;
|
| | |
|
| | | // Insert marker and check it's position relative to the selection
|
| | | parent.insertBefore(marker, nodes[index]);
|
| | | checkRng.moveToElementText(marker);
|
| | | position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
|
| | | if (position > 0) {
|
| | | // Marker is to the right
|
| | | startIndex = index + 1;
|
| | | } else if (position < 0) {
|
| | | // Marker is to the left
|
| | | endIndex = index - 1;
|
| | | } else {
|
| | | // Maker is where we are
|
| | | found = true;
|
| | | break;
|
| | | if (!sibling) {
|
| | | domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
|
| | | return;
|
| | | }
|
| | | }
|
| | |
|
| | | // Setup container
|
| | | container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
|
| | | if (!offset) {
|
| | | if (sibling.nodeType == 3)
|
| | | domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
|
| | | else
|
| | | domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
|
| | |
|
| | | // Handle element selection
|
| | | if (container.nodeType == 1) {
|
| | | dom.remove(marker);
|
| | | return;
|
| | | }
|
| | |
|
| | | // Find offset and container
|
| | | offset = dom.nodeIndex(container);
|
| | | container = container.parentNode;
|
| | | // Find the text node and offset
|
| | | while (sibling) {
|
| | | nodeValue = sibling.nodeValue;
|
| | | textNodeOffset += nodeValue.length;
|
| | |
|
| | | // Move the offset if we are setting the end or the position is after an element
|
| | | if (!start || index > 0)
|
| | | offset++;
|
| | | // 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;
|
| | | }
|
| | | } else {
|
| | | // Calculate offset within text node
|
| | | if (position > 0 || index == 0) {
|
| | | checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
|
| | | offset = checkRng.text.length;
|
| | | } else {
|
| | | checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
|
| | | offset = container.nodeValue.length - checkRng.text.length;
|
| | | // Find the text node and offset
|
| | | sibling = container.previousSibling;
|
| | |
|
| | | if (!sibling)
|
| | | return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
|
| | |
|
| | | // If there isn't any text to loop then use the first position
|
| | | if (!offset) {
|
| | | if (container.nodeType == 3)
|
| | | domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
|
| | | else
|
| | | domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
|
| | |
|
| | | return;
|
| | | }
|
| | |
|
| | | dom.remove(marker);
|
| | | while (sibling) {
|
| | | textNodeOffset += sibling.nodeValue.length;
|
| | |
|
| | | // We are at or passed the position we where looking for
|
| | | if (textNodeOffset >= offset) {
|
| | | container = sibling;
|
| | | textNodeOffset -= offset;
|
| | | break;
|
| | | }
|
| | |
|
| | | sibling = sibling.previousSibling;
|
| | | }
|
| | | }
|
| | |
|
| | | domRange[start ? 'setStart' : 'setEnd'](container, offset);
|
| | | domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
|
| | | };
|
| | |
|
| | | // Find start point
|
| | | findEndPoint(true);
|
| | | try {
|
| | | // Find start point
|
| | | findEndPoint(true);
|
| | |
|
| | | // Find end point if needed
|
| | | if (!collapsed)
|
| | | findEndPoint();
|
| | | // Find end point if needed
|
| | | if (!collapsed)
|
| | | findEndPoint();
|
| | | } catch (ex) {
|
| | | // IE has a nasty bug where text nodes might throw "invalid argument" when you
|
| | | // access the nodeValue or other properties of text nodes. This seems to happend when
|
| | | // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
|
| | | if (ex.number == -2147024809) {
|
| | | // Get the current selection
|
| | | bookmark = self.getBookmark(2);
|
| | |
|
| | | // Get start element
|
| | | tmpRange = ieRange.duplicate();
|
| | | tmpRange.collapse(true);
|
| | | element = tmpRange.parentElement();
|
| | |
|
| | | // Get end element
|
| | | if (!collapsed) {
|
| | | tmpRange = ieRange.duplicate();
|
| | | tmpRange.collapse(false);
|
| | | element2 = tmpRange.parentElement();
|
| | | element2.innerHTML = element2.innerHTML;
|
| | | }
|
| | |
|
| | | // Remove the broken elements
|
| | | element.innerHTML = element.innerHTML;
|
| | |
|
| | | // Restore the selection
|
| | | self.moveToBookmark(bookmark);
|
| | |
|
| | | // Since the range has moved we need to re-get it
|
| | | ieRange = selection.getRng();
|
| | |
|
| | | // Find start point
|
| | | findEndPoint(true);
|
| | |
|
| | | // Find end point if needed
|
| | | if (!collapsed)
|
| | | findEndPoint();
|
| | | } else
|
| | | throw ex; // Throw other errors
|
| | | }
|
| | |
|
| | | return domRange;
|
| | | };
|
| | |
|
| | | this.getBookmark = function(type) {
|
| | | var rng = selection.getRng(), start, end, bookmark = {};
|
| | |
|
| | | function getIndexes(node) {
|
| | | var node, parent, root, children, i, indexes = [];
|
| | |
|
| | | parent = node.parentNode;
|
| | | root = dom.getRoot().parentNode;
|
| | |
|
| | | while (parent != root) {
|
| | | children = parent.children;
|
| | |
|
| | | i = children.length;
|
| | | while (i--) {
|
| | | if (node === children[i]) {
|
| | | indexes.push(i);
|
| | | break;
|
| | | }
|
| | | }
|
| | |
|
| | | node = parent;
|
| | | parent = parent.parentNode;
|
| | | }
|
| | |
|
| | | return indexes;
|
| | | };
|
| | |
|
| | | function getBookmarkEndPoint(start) {
|
| | | var position;
|
| | |
|
| | | position = getPosition(rng, start);
|
| | | if (position) {
|
| | | return {
|
| | | position : position.position,
|
| | | offset : position.offset,
|
| | | indexes : getIndexes(position.node),
|
| | | inside : position.inside
|
| | | };
|
| | | }
|
| | | };
|
| | |
|
| | | // Non ubstructive bookmark
|
| | | if (type === 2) {
|
| | | // Handle text selection
|
| | | if (!rng.item) {
|
| | | bookmark.start = getBookmarkEndPoint(true);
|
| | |
|
| | | if (!selection.isCollapsed())
|
| | | bookmark.end = getBookmarkEndPoint();
|
| | | } else
|
| | | bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
|
| | | }
|
| | |
|
| | | return bookmark;
|
| | | };
|
| | |
|
| | | this.moveToBookmark = function(bookmark) {
|
| | | var rng, body = dom.doc.body;
|
| | |
|
| | | function resolveIndexes(indexes) {
|
| | | var node, i, idx, children;
|
| | |
|
| | | node = dom.getRoot();
|
| | | for (i = indexes.length - 1; i >= 0; i--) {
|
| | | children = node.children;
|
| | | idx = indexes[i];
|
| | |
|
| | | if (idx <= children.length - 1) {
|
| | | node = children[idx];
|
| | | }
|
| | | }
|
| | |
|
| | | return node;
|
| | | };
|
| | | |
| | | function setBookmarkEndPoint(start) {
|
| | | var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
|
| | |
|
| | | if (endPoint) {
|
| | | moveLeft = endPoint.position > 0;
|
| | |
|
| | | moveRng = body.createTextRange();
|
| | | moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
|
| | |
|
| | | offset = endPoint.offset;
|
| | | if (offset !== undef) {
|
| | | moveRng.collapse(endPoint.inside || moveLeft);
|
| | | moveRng.moveStart('character', moveLeft ? -offset : offset);
|
| | | } else
|
| | | moveRng.collapse(start);
|
| | |
|
| | | rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
|
| | |
|
| | | if (start)
|
| | | rng.collapse(true);
|
| | | }
|
| | | };
|
| | |
|
| | | if (bookmark.start) {
|
| | | if (bookmark.start.ctrl) {
|
| | | rng = body.createControlRange();
|
| | | rng.addElement(resolveIndexes(bookmark.start.indexes));
|
| | | rng.select();
|
| | | } else {
|
| | | rng = body.createTextRange();
|
| | | setBookmarkEndPoint(true);
|
| | | setBookmarkEndPoint();
|
| | | rng.select();
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | | this.addRange = function(rng) {
|
| | |
| | | tmpRng.moveToElementText(marker);
|
| | | } else {
|
| | | // Empty node selection for example <div>|</div>
|
| | | marker = doc.createTextNode(invisibleChar);
|
| | | marker = doc.createTextNode('\uFEFF');
|
| | | container.appendChild(marker);
|
| | | tmpRng.moveToElementText(marker.parentNode);
|
| | | tmpRng.collapse(TRUE);
|
| | |
| | | dom.remove(marker);
|
| | | }
|
| | | }
|
| | |
|
| | | // Destroy cached range
|
| | | this.destroy();
|
| | |
|
| | | // Setup some shorter versions
|
| | | startContainer = rng.startContainer;
|
| | |
| | | ieRng.select();
|
| | | };
|
| | |
|
| | | this.getRangeAt = function() {
|
| | | // Setup new range if the cache is empty
|
| | | if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
|
| | | range = getRange();
|
| | |
|
| | | // Store away text range for next call
|
| | | lastIERng = selection.getRng();
|
| | | }
|
| | |
|
| | | // IE will say that the range is equal then produce an invalid argument exception
|
| | | // if you perform specific operations in a keyup event. For example Ctrl+Del.
|
| | | // This hack will invalidate the range cache if the exception occurs
|
| | | try {
|
| | | range.startContainer.nextSibling;
|
| | | } catch (ex) {
|
| | | range = getRange();
|
| | | lastIERng = null;
|
| | | }
|
| | |
|
| | | // Return cached range
|
| | | return range;
|
| | | };
|
| | |
|
| | | this.destroy = function() {
|
| | | // Destroy cached range and last IE range to avoid memory leaks
|
| | | lastIERng = range = null;
|
| | | };
|
| | | // Expose range method
|
| | | this.getRangeAt = getRange;
|
| | | };
|
| | |
|
| | | // Expose the selection object
|
| | |
| | | return;
|
| | |
|
| | | try {
|
| | | // If IE is used, use the trick by Diego Perini
|
| | | // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
|
| | | // http://javascript.nwbox.com/IEContentLoaded/
|
| | | doc.documentElement.doScroll("left");
|
| | | } catch (ex) {
|
| | |
| | | tinymce.addUnload(t.destroy, t);
|
| | | },
|
| | |
|
| | | setCursorLocation: function(node, offset) {
|
| | | var t = this; var r = t.dom.createRng();
|
| | | r.setStart(node, offset);
|
| | | r.setEnd(node, offset);
|
| | | t.setRng(r);
|
| | | t.collapse(false);
|
| | | },
|
| | | getContent : function(s) {
|
| | | var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
|
| | |
|
| | |
| | | wb = wa = '';
|
| | | s.get = true;
|
| | | s.format = s.format || 'html';
|
| | | s.forced_root_block = '';
|
| | | t.onBeforeGetContent.dispatch(t, s);
|
| | |
|
| | | if (s.format == 'text')
|
| | |
| | |
|
| | | if (n)
|
| | | e.appendChild(n);
|
| | | } else if (is(r.item) || is(r.htmlText))
|
| | | e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
|
| | | else
|
| | | } else if (is(r.item) || is(r.htmlText)) {
|
| | | // IE will produce invalid markup if elements are present that
|
| | | // it doesn't understand like custom elements or HTML5 elements.
|
| | | // Adding a BR in front of the contents and then remoiving it seems to fix it though.
|
| | | e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
|
| | | e.removeChild(e.firstChild);
|
| | | } else
|
| | | e.innerHTML = r.toString();
|
| | |
|
| | | // Keep whitespace before and after
|
| | |
| | |
|
| | | // Remove the caret position
|
| | | self.dom.remove('__caret');
|
| | | self.setRng(rng);
|
| | |
|
| | | try {
|
| | | self.setRng(rng);
|
| | | } catch (ex) {
|
| | | // Might fail on Opera for some odd reason
|
| | | }
|
| | | } else {
|
| | | if (rng.item) {
|
| | | // Delete content and get caret text selection
|
| | |
| | | rng = self.getRng();
|
| | | }
|
| | |
|
| | | rng.pasteHTML(content);
|
| | | // Explorer removes spaces from the beginning of pasted contents
|
| | | if (/^\s+/.test(content)) {
|
| | | rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
|
| | | self.dom.remove('__mce_tmp');
|
| | | } else
|
| | | rng.pasteHTML(content);
|
| | | }
|
| | |
|
| | | // Dispatch set content event
|
| | |
| | | return bookmark;
|
| | | };
|
| | |
|
| | | if (t.tridentSel)
|
| | | return t.tridentSel.getBookmark(type);
|
| | |
|
| | | return getLocation();
|
| | | }
|
| | |
|
| | |
| | | moveToBookmark : function(bookmark) {
|
| | | var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
|
| | |
|
| | | // Clear selection cache
|
| | | if (t.tridentSel)
|
| | | t.tridentSel.destroy();
|
| | |
|
| | | if (bookmark) {
|
| | | if (bookmark.start) {
|
| | | rng = dom.createRng();
|
| | |
| | |
|
| | | return true;
|
| | | };
|
| | |
|
| | | if (t.tridentSel)
|
| | | return t.tridentSel.moveToBookmark(bookmark);
|
| | |
|
| | | if (setEndPoint(true) && setEndPoint()) {
|
| | | t.setRng(rng);
|
| | |
| | |
|
| | | if (s) {
|
| | | t.explicitRange = r;
|
| | | s.removeAllRanges();
|
| | |
|
| | | try {
|
| | | s.removeAllRanges();
|
| | | } catch (ex) {
|
| | | // IE9 might throw errors here don't know why
|
| | | }
|
| | |
|
| | | s.addRange(r);
|
| | | t.selectedRange = s.getRangeAt(0);
|
| | | }
|
| | |
| | | return bl;
|
| | | },
|
| | |
|
| | | normalize : function() {
|
| | | var self = this, rng, normalized;
|
| | |
|
| | | // Normalize only on non IE browsers for now
|
| | | if (tinymce.isIE)
|
| | | return;
|
| | |
|
| | | function normalizeEndPoint(start) {
|
| | | var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;
|
| | |
|
| | | container = rng[(start ? 'start' : 'end') + 'Container'];
|
| | | offset = rng[(start ? 'start' : 'end') + 'Offset'];
|
| | |
|
| | | // If the container is a document move it to the body element
|
| | | if (container.nodeType === 9) {
|
| | | container = container.body;
|
| | | offset = 0;
|
| | | }
|
| | |
|
| | | // If the container is body try move it into the closest text node or position
|
| | | // TODO: Add more logic here to handle element selection cases
|
| | | if (container === body) {
|
| | | // Resolve the index
|
| | | if (container.hasChildNodes()) {
|
| | | container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
|
| | | offset = 0;
|
| | |
|
| | | // Don't walk into elements that doesn't have any child nodes like a IMG
|
| | | if (container.hasChildNodes()) {
|
| | | // Walk the DOM to find a text node to place the caret at or a BR
|
| | | node = container;
|
| | | walker = new tinymce.dom.TreeWalker(container, body);
|
| | | do {
|
| | | // Found a text node use that position
|
| | | if (node.nodeType === 3) {
|
| | | offset = start ? 0 : node.nodeValue.length - 1;
|
| | | container = node;
|
| | | break;
|
| | | }
|
| | |
|
| | | // Found a BR element that we can place the caret before
|
| | | if (node.nodeName === 'BR') {
|
| | | offset = dom.nodeIndex(node);
|
| | | container = node.parentNode;
|
| | | break;
|
| | | }
|
| | | } while (node = (start ? walker.next() : walker.prev()));
|
| | |
|
| | | normalized = true;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // Set endpoint if it was normalized
|
| | | if (normalized)
|
| | | rng['set' + (start ? 'Start' : 'End')](container, offset);
|
| | | };
|
| | |
|
| | | rng = self.getRng();
|
| | |
|
| | | // Normalize the end points
|
| | | normalizeEndPoint(true);
|
| | | |
| | | if (rng.collapsed)
|
| | | normalizeEndPoint();
|
| | |
|
| | | // Set the selection if it was normalized
|
| | | if (normalized) {
|
| | | //console.log(self.dom.dumpRng(rng));
|
| | | self.setRng(rng);
|
| | | }
|
| | | },
|
| | |
|
| | | destroy : function(s) {
|
| | | var t = this;
|
| | |
|
| | | t.win = null;
|
| | |
|
| | | if (t.tridentSel)
|
| | | t.tridentSel.destroy();
|
| | |
|
| | | // Manual destroy then remove unload handler
|
| | | if (!s)
|
| | |
| | |
|
| | | // Explorer won't clone contents of script and style and the
|
| | | // selected index of select elements are cleared on a clone operation.
|
| | | if (isIE && dom.select('script,style,select').length > 0) {
|
| | | if (isIE && dom.select('script,style,select,map').length > 0) {
|
| | | content = node.innerHTML;
|
| | | node = node.cloneNode(false);
|
| | | dom.setHTML(node, content);
|
| | |
| | | args.content = htmlSerializer.serialize(
|
| | | htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
|
| | | );
|
| | |
|
| | | // Replace all BOM characters for now until we can find a better solution
|
| | | if (!args.cleanup)
|
| | | args.content = args.content.replace(/\uFEFF/g, '');
|
| | |
|
| | | // Post process
|
| | | if (!args.no_events)
|
| | |
| | | }
|
| | | });
|
| | | })(tinymce);
|
| | |
|
| | | (function(tinymce) {
|
| | | // Shorten class names
|
| | | var DOM = tinymce.DOM, is = tinymce.is;
|
| | |
| | |
|
| | | l = DOM.encode(s.label || '');
|
| | | h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
|
| | |
|
| | | if (s.image)
|
| | | if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) )
|
| | | h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
|
| | | else
|
| | | h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
|
| | |
| | | },
|
| | |
|
| | | selectByIndex : function(idx) {
|
| | | var t = this, e, o;
|
| | | var t = this, e, o, label;
|
| | |
|
| | | if (idx != t.selectedIndex) {
|
| | | e = DOM.get(t.id + '_text');
|
| | | label = DOM.get(t.id + '_voiceDesc');
|
| | | o = t.items[idx];
|
| | |
|
| | | if (o) {
|
| | | t.selectedValue = o.value;
|
| | | t.selectedIndex = idx;
|
| | | DOM.setHTML(e, DOM.encode(o.title));
|
| | | DOM.setHTML(label, t.settings.title + " - " + o.title);
|
| | | DOM.removeClass(e, 'mceTitle');
|
| | | DOM.setAttrib(t.id, 'aria-valuenow', o.title);
|
| | | } else {
|
| | | DOM.setHTML(e, DOM.encode(t.settings.title));
|
| | | DOM.setHTML(label, DOM.encode(t.settings.title));
|
| | | DOM.addClass(e, 'mceTitle');
|
| | | t.selectedValue = t.selectedIndex = null;
|
| | | DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
|
| | |
| | | renderHTML : function() {
|
| | | var h = '', t = this, s = t.settings, cp = t.classPrefix;
|
| | |
|
| | | h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
|
| | | h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
|
| | | h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
|
| | | h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
|
| | | h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
|
| | |
| | | },
|
| | |
|
| | | showMenu : function() {
|
| | | var t = this, p1, p2, e = DOM.get(this.id), m;
|
| | | var t = this, p2, e = DOM.get(this.id), m;
|
| | |
|
| | | if (t.isDisabled() || t.items.length == 0)
|
| | | return;
|
| | |
| | | t.isMenuRendered = true;
|
| | | }
|
| | |
|
| | | p1 = DOM.getPos(this.settings.menu_container);
|
| | | p2 = DOM.getPos(e);
|
| | |
|
| | | m = t.menu;
|
| | |
| | | if (o.value === undefined) {
|
| | | m.add({
|
| | | title : o.title,
|
| | | role : "option",
|
| | | 'class' : 'mceMenuItemTitle',
|
| | | onclick : function() {
|
| | | if (t.settings.onselect('') !== false)
|
| | |
| | | });
|
| | | } else {
|
| | | o.id = DOM.uniqueId();
|
| | | o.role= "option";
|
| | | o.onclick = function() {
|
| | | if (t.settings.onselect(o.value) !== false)
|
| | | t.select(o.value); // Must be runned after
|
| | |
| | | }
|
| | | });
|
| | | })(tinymce);
|
| | |
|
| | | (function(tinymce) {
|
| | | var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
|
| | |
|
| | |
| | | Event.remove(t.id, 'blur', bf);
|
| | | });
|
| | |
|
| | | //prevent default left and right keys on chrome - so that the keyboard navigation is used.
|
| | | if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
|
| | | return Event.prevent(e);
|
| | | }
|
| | | |
| | | if (e.keyCode == 13 || e.keyCode == 32) {
|
| | | onChange(e);
|
| | | return Event.cancel(e);
|
| | |
| | | }
|
| | | });
|
| | | })(tinymce);
|
| | |
|
| | | (function(tinymce) {
|
| | | var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
|
| | |
|
| | |
| | | h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
|
| | |
|
| | | h += '</tr></tbody>';
|
| | | h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
|
| | | return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
|
| | | h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
|
| | | return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
|
| | | },
|
| | |
|
| | | postRender : function() {
|
| | |
| | | hideMenu : function(e) {
|
| | | var t = this;
|
| | |
|
| | | // Prevent double toogles by canceling the mouse click event to the button
|
| | | if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
|
| | | return;
|
| | | if (t.isMenuVisible) {
|
| | | // Prevent double toogles by canceling the mouse click event to the button
|
| | | if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
|
| | | return;
|
| | |
|
| | | if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
|
| | | DOM.removeClass(t.id, 'mceSplitButtonSelected');
|
| | | Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
|
| | | Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
|
| | | DOM.hide(t.id + '_menu');
|
| | | if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
|
| | | DOM.removeClass(t.id, 'mceSplitButtonSelected');
|
| | | Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
|
| | | Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
|
| | | DOM.hide(t.id + '_menu');
|
| | | }
|
| | |
|
| | | t.isMenuVisible = 0;
|
| | | t.onHideMenu.dispatch();
|
| | | }
|
| | |
|
| | | t.onHideMenu.dispatch(t);
|
| | |
|
| | | t.isMenuVisible = 0;
|
| | | t.editor.focus();
|
| | | },
|
| | |
|
| | | renderMenu : function() {
|
| | |
| | | },
|
| | |
|
| | | focus : function() {
|
| | | this.keyNav.focus();
|
| | | var t = this;
|
| | | dom.get(t.id).focus();
|
| | | },
|
| | |
|
| | | postRender : function() {
|
| | |
| | | root: t.id,
|
| | | items: items,
|
| | | onCancel: function() {
|
| | | //Move focus if webkit so that navigation back will read the item.
|
| | | if (tinymce.isWebKit) {
|
| | | dom.get(t.editor.id+"_ifr").focus();
|
| | | }
|
| | | t.editor.focus();
|
| | | },
|
| | | excludeFromTabOrder: !t.settings.tab_focus_toolbar
|
| | |
| | |
|
| | | (function(tinymce) {
|
| | | // Shorten class names
|
| | | var dom = tinymce.DOM, each = tinymce.each
|
| | | var dom = tinymce.DOM, each = tinymce.each;
|
| | | tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
|
| | | renderHTML : function() {
|
| | | var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
|
| | |
| | | },
|
| | |
|
| | | get : function(n) {
|
| | | return this.lookup[n];
|
| | | if (this.lookup[n]) {
|
| | | return this.lookup[n].instance;
|
| | | } else {
|
| | | return undefined;
|
| | | }
|
| | | },
|
| | |
|
| | | dependencies : function(n) {
|
| | | var result;
|
| | | if (this.lookup[n]) {
|
| | | result = this.lookup[n].dependencies;
|
| | | }
|
| | | return result || [];
|
| | | },
|
| | |
|
| | | requireLangPack : function(n) {
|
| | |
| | | tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
|
| | | },
|
| | |
|
| | | add : function(id, o) {
|
| | | add : function(id, o, dependencies) {
|
| | | this.items.push(o);
|
| | | this.lookup[id] = o;
|
| | | this.lookup[id] = {instance:o, dependencies:dependencies};
|
| | | this.onAdd.dispatch(this, id, o);
|
| | |
|
| | | return o;
|
| | | },
|
| | | createUrl: function(baseUrl, dep) {
|
| | | if (typeof dep === "object") {
|
| | | return dep
|
| | | } else {
|
| | | return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
|
| | | }
|
| | | },
|
| | |
|
| | | addComponents: function(pluginName, scripts) {
|
| | | var pluginUrl = this.urls[pluginName];
|
| | | tinymce.each(scripts, function(script){
|
| | | tinymce.ScriptLoader.add(pluginUrl+"/"+script); |
| | | });
|
| | | },
|
| | |
|
| | | load : function(n, u, cb, s) {
|
| | | var t = this;
|
| | | var t = this, url = u;
|
| | |
|
| | | function loadDependencies() {
|
| | | var dependencies = t.dependencies(n);
|
| | | tinymce.each(dependencies, function(dep) {
|
| | | var newUrl = t.createUrl(u, dep);
|
| | | t.load(newUrl.resource, newUrl, undefined, undefined);
|
| | | });
|
| | | if (cb) {
|
| | | if (s) {
|
| | | cb.call(s);
|
| | | } else {
|
| | | cb.call(tinymce.ScriptLoader);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | if (t.urls[n])
|
| | | return;
|
| | | if (typeof u === "object")
|
| | | url = u.prefix + u.resource + u.suffix;
|
| | |
|
| | | if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
|
| | | u = tinymce.baseURL + '/' + u;
|
| | | if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
|
| | | url = tinymce.baseURL + '/' + url;
|
| | |
|
| | | t.urls[n] = u.substring(0, u.lastIndexOf('/'));
|
| | | t.urls[n] = url.substring(0, url.lastIndexOf('/'));
|
| | |
|
| | | if (!t.lookup[n])
|
| | | tinymce.ScriptLoader.add(u, cb, s);
|
| | | if (t.lookup[n]) {
|
| | | loadDependencies();
|
| | | } else {
|
| | | tinymce.ScriptLoader.add(url, loadDependencies, s);
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | if (!t.getElement())
|
| | | return;
|
| | |
|
| | | // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
|
| | | // browser says it has contentEditable support but there is no visible caret
|
| | | // We will remove this check ones Apple implements full contentEditable support
|
| | | if (tinymce.isIDevice)
|
| | | // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff |
| | | // here since the browser says it has contentEditable support but there is no visible
|
| | | // caret We will remove this check ones Apple implements full contentEditable support
|
| | | if (tinymce.isIDevice && !tinymce.isIOS5)
|
| | | return;
|
| | |
|
| | | // Add hidden input for non input elements inside form elements
|
| | |
| | | ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
|
| | |
|
| | | each(explode(s.plugins), function(p) {
|
| | | if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
|
| | | // Skip safari plugin, since it is removed as of 3.3b1
|
| | | if (p == 'safari')
|
| | | return;
|
| | |
|
| | | PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
|
| | | if (p &&!PluginManager.urls[p]) {
|
| | | if (p.charAt(0) == '-') {
|
| | | p = p.substr(1, p.length);
|
| | | var dependencies = PluginManager.dependencies(p);
|
| | | each(dependencies, function(dep) {
|
| | | var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
|
| | | var dep = PluginManager.createUrl(defaultSettings, dep);
|
| | | PluginManager.load(dep.resource, dep);
|
| | | |
| | | });
|
| | | } else {
|
| | | // Skip safari plugin, since it is removed as of 3.3b1
|
| | | if (p == 'safari') {
|
| | | return;
|
| | | }
|
| | | PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | },
|
| | |
|
| | | init : function() {
|
| | | var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i;
|
| | | var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
|
| | |
|
| | | tinymce.add(t);
|
| | |
|
| | |
| | | if (t.theme.init && s.init_theme)
|
| | | t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
|
| | | }
|
| | |
|
| | | // Create all plugins
|
| | | each(explode(s.plugins.replace(/\-/g, '')), function(p) {
|
| | | function initPlugin(p) {
|
| | | var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
|
| | |
|
| | | if (c) {
|
| | | if (c && tinymce.inArray(initializedPlugins,p) === -1) {
|
| | | each(PluginManager.dependencies(p), function(dep){
|
| | | initPlugin(dep);
|
| | | });
|
| | | po = new c(t, u);
|
| | |
|
| | | t.plugins[p] = po;
|
| | |
|
| | | if (po.init)
|
| | | if (po.init) {
|
| | | po.init(t, u);
|
| | | initializedPlugins.push(p);
|
| | | }
|
| | | }
|
| | | });
|
| | | }
|
| | | |
| | | // Create all plugins
|
| | | each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
|
| | |
|
| | | // Setup popup CSS path(s)
|
| | | if (s.popup_css !== false) {
|
| | |
| | |
|
| | | t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
|
| | |
|
| | | // Firefox 2 doesn't load stylesheets correctly this way
|
| | | if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
|
| | | for (i = 0; i < t.contentCSS.length; i++)
|
| | | t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
|
| | |
|
| | | t.contentCSS = [];
|
| | | // Load the CSS by injecting them into the HTML this will reduce "flicker"
|
| | | for (i = 0; i < t.contentCSS.length; i++) {
|
| | | t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
|
| | | }
|
| | |
|
| | | bi = s.body_id || 'tinymce';
|
| | |
| | | bc = bc[t.id] || '';
|
| | | }
|
| | |
|
| | | t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
|
| | | t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"><br></body></html>';
|
| | |
|
| | | // Domain relaxing enabled, then set document domain
|
| | | if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
|
| | | // We need to write the contents here in IE since multiple writes messes up refresh button and back button
|
| | | u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; |
| | | u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
|
| | | }
|
| | |
|
| | | // Create iframe
|
| | |
| | | n = DOM.add(o.iframeContainer, 'iframe', {
|
| | | id : t.id + "_ifr",
|
| | | src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
|
| | | frameBorder : '0', |
| | | frameBorder : '0',
|
| | | allowTransparency : "true",
|
| | | title : s.aria_label,
|
| | | style : {
|
| | | width : '100%',
|
| | | height : h
|
| | | height : h,
|
| | | display : 'block' // Important for Gecko to render the iframe correctly
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | d.domain = tinymce.relaxedDomain;
|
| | | }
|
| | |
|
| | | // Design mode needs to be added here Ctrl+A will fail otherwise
|
| | | if (!isIE) {
|
| | | try {
|
| | | if (!s.readonly)
|
| | | d.designMode = 'On';
|
| | | } catch (ex) {
|
| | | // Will fail on Gecko if the editor is placed in an hidden container element
|
| | | // The design mode will be set ones the editor is focused
|
| | | }
|
| | | }
|
| | | // It will not steal focus while setting contentEditable
|
| | | b = t.getBody();
|
| | | b.disabled = true;
|
| | |
|
| | | // IE needs to use contentEditable or it will display non secure items for HTTPS
|
| | | if (isIE) {
|
| | | // It will not steal focus if we hide it while setting contentEditable
|
| | | b = t.getBody();
|
| | | DOM.hide(b);
|
| | | if (!s.readonly)
|
| | | b.contentEditable = true;
|
| | |
|
| | | if (!s.readonly)
|
| | | b.contentEditable = true;
|
| | |
|
| | | DOM.show(b);
|
| | | }
|
| | | b.disabled = false;
|
| | |
|
| | | t.schema = new tinymce.html.Schema(s);
|
| | |
|
| | |
| | |
|
| | | t.parser = new tinymce.html.DomParser(s, t.schema);
|
| | |
|
| | | // Force anchor names closed
|
| | | t.parser.addAttributeFilter('name', function(nodes, name) {
|
| | | var i = nodes.length, sibling, prevSibling, parent, node;
|
| | |
|
| | | while (i--) {
|
| | | node = nodes[i];
|
| | | if (node.name === 'a' && node.firstChild) {
|
| | | parent = node.parent;
|
| | |
|
| | | // Move children after current node
|
| | | sibling = node.lastChild;
|
| | | do {
|
| | | prevSibling = sibling.prev;
|
| | | parent.insert(sibling, node);
|
| | | sibling = prevSibling;
|
| | | } while (sibling);
|
| | | // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
|
| | | if (!t.settings.allow_html_in_named_anchor) {
|
| | | t.parser.addAttributeFilter('name', function(nodes, name) {
|
| | | var i = nodes.length, sibling, prevSibling, parent, node;
|
| | | |
| | | while (i--) {
|
| | | node = nodes[i];
|
| | | if (node.name === 'a' && node.firstChild) {
|
| | | parent = node.parent;
|
| | | |
| | | // Move children after current node
|
| | | sibling = node.lastChild;
|
| | | do {
|
| | | prevSibling = sibling.prev;
|
| | | parent.insert(sibling, node);
|
| | | sibling = prevSibling;
|
| | | } while (sibling);
|
| | | }
|
| | | }
|
| | | }
|
| | | });
|
| | | });
|
| | | }
|
| | |
|
| | | // Convert src and href into data-mce-src, data-mce-href and data-mce-style
|
| | | t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
|
| | | var i = nodes.length, node, dom = t.dom, value;
|
| | | var i = nodes.length, node, dom = t.dom, value, internalName;
|
| | |
|
| | | while (i--) {
|
| | | node = nodes[i];
|
| | | value = node.attr(name);
|
| | | internalName = 'data-mce-' + name;
|
| | |
|
| | | if (name === "style")
|
| | | node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name));
|
| | | else
|
| | | node.attr('data-mce-' + name, t.convertURL(value, name, node.name));
|
| | | // Add internal attribute if we need to we don't on a refresh of the document
|
| | | if (!node.attributes.map[internalName]) { |
| | | if (name === "style")
|
| | | node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
|
| | | else
|
| | | node.attr(internalName, t.convertURL(value, name, node.name));
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | subscript : {inline : 'sub'},
|
| | | superscript : {inline : 'sup'},
|
| | |
|
| | | link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
|
| | | onmatch : function(node) {
|
| | | return true;
|
| | | },
|
| | |
|
| | | onformat : function(elm, fmt, vars) {
|
| | | each(vars, function(value, key) {
|
| | | t.dom.setAttrib(elm, key, value);
|
| | | });
|
| | | }
|
| | | },
|
| | |
|
| | | removeformat : [
|
| | | {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
|
| | | {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
|
| | |
| | |
|
| | | t.controlManager.onPostRender.dispatch(t, t.controlManager);
|
| | | t.onPostRender.dispatch(t);
|
| | |
|
| | | t.quirks = new tinymce.util.Quirks(this);
|
| | |
|
| | | if (s.directionality)
|
| | | t.getBody().dir = s.directionality;
|
| | |
| | | });
|
| | |
|
| | | t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
|
| | |
|
| | | if (!s.readonly) {
|
| | | try {
|
| | | // Design mode must be set here once again to fix a bug where
|
| | | // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
|
| | | d.designMode = 'Off';
|
| | | d.designMode = 'On';
|
| | | } catch (ex) {
|
| | | // Will fail on Gecko if the editor is placed in an hidden container element
|
| | | // The design mode will be set ones the editor is focused
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // A small timeout was needed since firefox will remove. Bug: #1838304
|
| | | setTimeout(function () {
|
| | | if (t.removed)
|
| | | return;
|
| | | t.load({initial : true, format : 'html'});
|
| | | t.startContent = t.getContent({format : 'raw'});
|
| | | t.undoManager.add();
|
| | | t.initialized = true;
|
| | |
|
| | | t.load({initial : true, format : 'html'});
|
| | | t.startContent = t.getContent({format : 'raw'});
|
| | | t.undoManager.add();
|
| | | t.initialized = true;
|
| | | t.onInit.dispatch(t);
|
| | | t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
|
| | | t.execCallback('init_instance_callback', t);
|
| | | t.focus(true);
|
| | | t.nodeChanged({initial : 1});
|
| | |
|
| | | t.onInit.dispatch(t);
|
| | | t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
|
| | | t.execCallback('init_instance_callback', t);
|
| | | t.focus(true);
|
| | | t.nodeChanged({initial : 1});
|
| | | // Load specified content CSS last
|
| | | each(t.contentCSS, function(u) {
|
| | | t.dom.loadCSS(u);
|
| | | });
|
| | |
|
| | | // Load specified content CSS last
|
| | | each(t.contentCSS, function(u) {
|
| | | t.dom.loadCSS(u);
|
| | | });
|
| | | // Handle auto focus
|
| | | if (s.auto_focus) {
|
| | | setTimeout(function () {
|
| | | var ed = tinymce.get(s.auto_focus);
|
| | |
|
| | | // Handle auto focus
|
| | | if (s.auto_focus) {
|
| | | setTimeout(function () {
|
| | | var ed = tinymce.get(s.auto_focus);
|
| | | ed.selection.select(ed.getBody(), 1);
|
| | | ed.selection.collapse(1);
|
| | | ed.getBody().focus();
|
| | | ed.getWin().focus();
|
| | | }, 100);
|
| | | }
|
| | |
|
| | | ed.selection.select(ed.getBody(), 1);
|
| | | ed.selection.collapse(1);
|
| | | ed.getWin().focus();
|
| | | }, 100);
|
| | | }
|
| | | }, 1);
|
| | | |
| | | e = null;
|
| | | },
|
| | |
|
| | |
|
| | | focus : function(sf) {
|
| | | var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
|
| | | var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
|
| | |
|
| | | if (!sf) {
|
| | | // Get selected control element
|
| | | ieRng = t.selection.getRng();
|
| | | ieRng = selection.getRng();
|
| | | if (ieRng.item) {
|
| | | controlElm = ieRng.item(0);
|
| | | }
|
| | |
|
| | | t._refreshContentEditable();
|
| | | selection.normalize();
|
| | |
|
| | | // Is not content editable
|
| | | if (!ce)
|
| | | t.getWin().focus();
|
| | |
|
| | | // Focus the body as well since it's contentEditable
|
| | | if (tinymce.isGecko) {
|
| | | t.getBody().focus();
|
| | | }
|
| | |
|
| | | // Restore selected control element
|
| | | // This is needed when for example an image is selected within a
|
| | |
| | | },
|
| | |
|
| | | setContent : function(content, args) {
|
| | | var self = this, rootNode, body = self.getBody();
|
| | | var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
|
| | |
|
| | | // Setup args object
|
| | | args = args || {};
|
| | |
| | | // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
|
| | | // It will also be impossible to place the caret in the editor unless there is a BR element present
|
| | | if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
|
| | | body.innerHTML = '<br data-mce-bogus="1" />';
|
| | | forcedRootBlockName = self.settings.forced_root_block;
|
| | | if (forcedRootBlockName)
|
| | | content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
|
| | | else
|
| | | content = '<br data-mce-bogus="1">';
|
| | |
|
| | | body.innerHTML = content;
|
| | | self.selection.select(body, true);
|
| | | self.selection.collapse(true);
|
| | | return;
|
| | | }
|
| | |
|
| | |
| | | // Do post processing
|
| | | if (!args.no_events)
|
| | | self.onSetContent.dispatch(self, args);
|
| | |
|
| | | self.selection.normalize();
|
| | |
|
| | | return args.content;
|
| | | },
|
| | |
| | | var t = this, d = t.getDoc(), s = t.settings;
|
| | |
|
| | | if (isGecko && !s.readonly) {
|
| | | if (t._isHidden()) {
|
| | | try {
|
| | | if (!s.content_editable)
|
| | | d.designMode = 'On';
|
| | | } catch (ex) {
|
| | | // Fails if it's hidden
|
| | | }
|
| | | }
|
| | | t._refreshContentEditable();
|
| | |
|
| | | try {
|
| | | // Try new Gecko method
|
| | |
| | | t.onMouseDown.add(setOpts);
|
| | | }
|
| | |
|
| | | // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
|
| | | // WebKit can't even do simple things like selecting an image
|
| | | // This also fixes so it's possible to select mceItemAnchors
|
| | | if (tinymce.isWebKit) {
|
| | | t.onClick.add(function(ed, e) {
|
| | | e = e.target;
|
| | |
|
| | | // Needs tobe the setBaseAndExtend or it will fail to select floated images
|
| | | if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
|
| | | t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
|
| | | t.nodeChanged();
|
| | | }
|
| | | });
|
| | | }
|
| | |
|
| | | // Add node change handlers
|
| | | t.onMouseUp.add(t.nodeChanged);
|
| | | //t.onClick.add(t.nodeChanged);
|
| | |
| | | if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
|
| | | t.nodeChanged();
|
| | | });
|
| | |
|
| | |
|
| | | // Add block quote deletion handler
|
| | | t.onKeyDown.add(function(ed, e) {
|
| | | // Was the BACKSPACE key pressed?
|
| | | if (e.keyCode != 8)
|
| | | return;
|
| | |
|
| | | var n = ed.selection.getRng().startContainer;
|
| | | var offset = ed.selection.getRng().startOffset;
|
| | |
|
| | | while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
|
| | | n = n.parentNode;
|
| | | |
| | | // Is the cursor at the beginning of a blockquote?
|
| | | if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
|
| | | // Remove the blockquote
|
| | | ed.formatter.toggle('blockquote', null, n.parentNode);
|
| | |
|
| | | // Move the caret to the beginning of n
|
| | | var rng = ed.selection.getRng();
|
| | | rng.setStart(n, 0);
|
| | | rng.setEnd(n, 0);
|
| | | ed.selection.setRng(rng);
|
| | | ed.selection.collapse(false);
|
| | | }
|
| | | });
|
| | | |
| | |
|
| | |
|
| | | // Add reset handler
|
| | | t.onReset.add(function() {
|
| | |
| | | for (i=1; i<=6; i++)
|
| | | t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
|
| | |
|
| | | t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
|
| | | t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
|
| | | t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
|
| | | t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
|
| | | t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
|
| | | t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
|
| | |
|
| | | function find(e) {
|
| | | var v = null;
|
| | |
| | | cb : cb
|
| | | };
|
| | | });
|
| | |
|
| | | t.onKeyDown.add(function(ed, e) {
|
| | | var sel;
|
| | |
|
| | | switch (e.keyCode) {
|
| | | case 8:
|
| | | sel = t.getDoc().selection;
|
| | |
|
| | | // Fix IE control + backspace browser bug
|
| | | if (sel.createRange && sel.createRange().item) {
|
| | | ed.dom.remove(sel.createRange().item(0));
|
| | | return Event.cancel(e);
|
| | | }
|
| | | }
|
| | | });
|
| | | }
|
| | |
|
| | | if (tinymce.isOpera) {
|
| | |
| | | });
|
| | |
|
| | | t.onKeyUp.add(function(ed, e) {
|
| | | var rng, parent, bookmark;
|
| | | var keyCode = e.keyCode;
|
| | |
|
| | | // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node.
|
| | | if (isIE && e.keyCode == 8) {
|
| | | rng = t.selection.getRng();
|
| | | if (rng.parentElement) {
|
| | | parent = rng.parentElement();
|
| | | bookmark = t.selection.getBookmark();
|
| | | parent.innerHTML = parent.innerHTML;
|
| | | t.selection.moveToBookmark(bookmark);
|
| | | }
|
| | | }
|
| | |
|
| | | if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
|
| | | if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
|
| | | addUndo();
|
| | | });
|
| | |
|
| | | t.onKeyDown.add(function(ed, e) {
|
| | | var rng, parent, bookmark, keyCode = e.keyCode;
|
| | | var keyCode = e.keyCode, sel;
|
| | |
|
| | | // IE has a really odd bug where the DOM might include an node that doesn't have
|
| | | // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
|
| | | // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
|
| | | // after you delete contents from it. See: #3008923
|
| | | if (isIE && keyCode == 46) {
|
| | | rng = t.selection.getRng();
|
| | | if (keyCode == 8) {
|
| | | sel = t.getDoc().selection;
|
| | |
|
| | | if (rng.parentElement) {
|
| | | parent = rng.parentElement();
|
| | | // Fix IE control + backspace browser bug
|
| | | if (sel && sel.createRange && sel.createRange().item) {
|
| | | t.undoManager.beforeChange();
|
| | | ed.dom.remove(sel.createRange().item(0));
|
| | | addUndo();
|
| | |
|
| | | if (!t.undoManager.typing) {
|
| | | t.undoManager.beforeChange();
|
| | | t.undoManager.typing = true;
|
| | | t.undoManager.add();
|
| | | }
|
| | |
|
| | | // Select next word when ctrl key is used in combo with delete
|
| | | if (e.ctrlKey) {
|
| | | rng.moveEnd('word', 1);
|
| | | rng.select();
|
| | | }
|
| | |
|
| | | // Delete contents
|
| | | t.selection.getSel().clear();
|
| | |
|
| | | // Check if we are within the same parent
|
| | | if (rng.parentElement() == parent) {
|
| | | bookmark = t.selection.getBookmark();
|
| | |
|
| | | try {
|
| | | // Update the HTML and hopefully it will remove the artifacts
|
| | | parent.innerHTML = parent.innerHTML;
|
| | | } catch (ex) {
|
| | | // And since it's IE it can sometimes produce an unknown runtime error
|
| | | }
|
| | |
|
| | | // Restore the caret position
|
| | | t.selection.moveToBookmark(bookmark);
|
| | | }
|
| | |
|
| | | // Block the default delete behavior since it might be broken
|
| | | e.preventDefault();
|
| | | return;
|
| | | return Event.cancel(e);
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | // If key isn't shift,ctrl,alt,capslock,metakey
|
| | | if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
|
| | | t.undoManager.beforeChange();
|
| | | t.undoManager.add();
|
| | | t.undoManager.typing = true;
|
| | | t.undoManager.add();
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | addUndo();
|
| | | });
|
| | | }
|
| | | |
| | |
|
| | | // Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5
|
| | | // It only fires the nodeChange event every 50ms since it would other wise update the UI when you type and it hogs the CPU
|
| | | if (tinymce.isWebKit) {
|
| | | dom.bind(t.getDoc(), 'selectionchange', function() {
|
| | | if (t.selectionTimer) {
|
| | | clearTimeout(t.selectionTimer);
|
| | | t.selectionTimer = 0;
|
| | | }
|
| | |
|
| | | t.selectionTimer = window.setTimeout(function() {
|
| | | t.nodeChanged();
|
| | | }, 50);
|
| | | });
|
| | | }
|
| | |
|
| | | // Bug fix for FireFox keeping styles from end of selection instead of start.
|
| | | if (tinymce.isGecko) {
|
| | | function getAttributeApplyFunction() {
|
| | |
| | |
|
| | | return function() {
|
| | | var target = t.selection.getStart();
|
| | | t.dom.removeAllAttribs(target);
|
| | | each(template, function(attr) {
|
| | | target.setAttributeNode(attr.cloneNode(true));
|
| | | });
|
| | |
|
| | | if (target !== t.getBody()) {
|
| | | t.dom.removeAllAttribs(target);
|
| | |
|
| | | each(template, function(attr) {
|
| | | target.setAttributeNode(attr.cloneNode(true));
|
| | | });
|
| | | }
|
| | | };
|
| | | }
|
| | |
|
| | |
| | | }
|
| | | },
|
| | |
|
| | | _refreshContentEditable : function() {
|
| | | var self = this, body, parent;
|
| | |
|
| | | // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
|
| | | if (self._isHidden()) {
|
| | | body = self.getBody();
|
| | | parent = body.parentNode;
|
| | |
|
| | | parent.removeChild(body);
|
| | | parent.appendChild(body);
|
| | |
|
| | | body.focus();
|
| | | }
|
| | | },
|
| | |
|
| | | _isHidden : function() {
|
| | | var s;
|
| | |
|
| | |
| | | selection = editor.selection,
|
| | | commands = {state: {}, exec : {}, value : {}},
|
| | | settings = editor.settings,
|
| | | formatter = editor.formatter,
|
| | | bookmark;
|
| | |
|
| | | function execCommand(command, ui, value) {
|
| | |
| | | };
|
| | |
|
| | | function isFormatMatch(name) {
|
| | | return editor.formatter.match(name);
|
| | | return formatter.match(name);
|
| | | };
|
| | |
|
| | | function toggleFormat(name, value) {
|
| | | editor.formatter.toggle(name, value ? {value : value} : undefined);
|
| | | formatter.toggle(name, value ? {value : value} : undefined);
|
| | | };
|
| | |
|
| | | function storeSelection(type) {
|
| | |
| | | // Remove all other alignments first
|
| | | each('left,center,right,full'.split(','), function(name) {
|
| | | if (align != name)
|
| | | editor.formatter.remove('align' + name);
|
| | | formatter.remove('align' + name);
|
| | | });
|
| | |
|
| | | toggleFormat('align' + align);
|
| | |
| | | },
|
| | |
|
| | | RemoveFormat : function(command) {
|
| | | editor.formatter.remove(command);
|
| | | formatter.remove(command);
|
| | | },
|
| | |
|
| | | mceBlockQuote : function(command) {
|
| | |
| | | },
|
| | |
|
| | | mceInsertContent : function(command, ui, value) {
|
| | | var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
|
| | | var parser, serializer, parentNode, rootNode, fragment, args,
|
| | | marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
|
| | |
|
| | | function findSuitableCaretNode(start_node, root_node) {
|
| | | var node, walker = new tinymce.dom.TreeWalker(start_node, root_node);
|
| | | // Setup parser and serializer
|
| | | parser = editor.parser;
|
| | | serializer = new tinymce.html.Serializer({}, editor.schema);
|
| | | bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
|
| | |
|
| | | while ((node = walker.current())) {
|
| | | if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
|
| | | return node;
|
| | |
|
| | | walker.prev();
|
| | | }
|
| | | };
|
| | |
|
| | | // Run beforeSetContent handlers on the HTML to be inserted
|
| | | args = {content: value, format: 'html'};
|
| | | selection.onBeforeSetContent.dispatch(selection, args);
|
| | | value = args.content;
|
| | |
| | | if (value.indexOf('{$caret}') == -1)
|
| | | value += '{$caret}';
|
| | |
|
| | | // Set the content at selection to a span and replace it's contents with the value
|
| | | selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
|
| | | dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
|
| | | // Replace the caret marker with a span bookmark element
|
| | | value = value.replace(/\{\$caret\}/, bookmarkHtml);
|
| | |
|
| | | caretNode = dom.select('#__mce')[0];
|
| | | rootNode = dom.getRoot();
|
| | | // Insert node maker where we will insert the new HTML and get it's parent
|
| | | if (!selection.isCollapsed())
|
| | | editor.getDoc().execCommand('Delete', false, null);
|
| | |
|
| | | // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
|
| | | if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
|
| | | node = findSuitableCaretNode(caretNode.previousSibling, rootNode);
|
| | | if (node) {
|
| | | if (node.nodeName == 'BR')
|
| | | node.parentNode.insertBefore(caretNode, node);
|
| | | else
|
| | | dom.insertAfter(caretNode, node);
|
| | | }
|
| | | }
|
| | | parentNode = selection.getNode();
|
| | |
|
| | | // Find caret root parent and clean it up using the serializer to avoid nesting
|
| | | while (caretNode) {
|
| | | if (caretNode === rootNode) {
|
| | | // Clean up the parent element by parsing and serializing it
|
| | | // This will remove invalid elements/attributes and fix nesting issues
|
| | | dom.setOuterHTML(parent, |
| | | new tinymce.html.Serializer({}, editor.schema).serialize(
|
| | | new tinymce.html.DomParser({
|
| | | remove_trailing_brs : true
|
| | | }, editor.schema).parse(dom.getOuterHTML(parent))
|
| | | )
|
| | | );
|
| | | // Parse the fragment within the context of the parent node
|
| | | args = {context : parentNode.nodeName.toLowerCase()};
|
| | | fragment = parser.parse(value, args);
|
| | |
|
| | | break;
|
| | | }
|
| | | // Move the caret to a more suitable location
|
| | | node = fragment.lastChild;
|
| | | if (node.attr('id') == 'mce_marker') {
|
| | | marker = node;
|
| | |
|
| | | parent = caretNode;
|
| | | caretNode = caretNode.parentNode;
|
| | | }
|
| | |
|
| | | // Find caret after cleanup and move selection to that location
|
| | | caretNode = dom.select('#__mce')[0];
|
| | | if (caretNode) {
|
| | | node = findSuitableCaretNode(caretNode.previousSibling, rootNode);
|
| | | dom.remove(caretNode);
|
| | |
|
| | | if (node) {
|
| | | rng = dom.createRng();
|
| | |
|
| | | if (node.nodeType == 3) {
|
| | | rng.setStart(node, node.length);
|
| | | rng.setEnd(node, node.length);
|
| | | } else {
|
| | | if (node.nodeName == 'BR') {
|
| | | rng.setStartBefore(node);
|
| | | rng.setEndBefore(node);
|
| | | } else {
|
| | | rng.setStartAfter(node);
|
| | | rng.setEndAfter(node);
|
| | | }
|
| | | for (node = node.prev; node; node = node.walk(true)) {
|
| | | if (node.type == 3 || !dom.isBlock(node.name)) {
|
| | | node.parent.insert(marker, node, node.name === 'br');
|
| | | break;
|
| | | }
|
| | |
|
| | | selection.setRng(rng);
|
| | |
|
| | | // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
|
| | | if (!tinymce.isIE) {
|
| | | node = dom.create('span', null, '\u00a0');
|
| | | rng.insertNode(node);
|
| | | nodeRect = dom.getRect(node);
|
| | | viewPortRect = dom.getViewPort(editor.getWin());
|
| | |
|
| | | // Check if node is out side the viewport if it is then scroll to it
|
| | | if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
|
| | | (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
|
| | | editor.getBody().scrollLeft = nodeRect.x;
|
| | | editor.getBody().scrollTop = nodeRect.y;
|
| | | }
|
| | |
|
| | | dom.remove(node);
|
| | | }
|
| | |
|
| | | // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
|
| | | // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
|
| | | selection.collapse(true);
|
| | | }
|
| | | }
|
| | |
|
| | | // If parser says valid we can insert the contents into that parent
|
| | | if (!args.invalid) {
|
| | | value = serializer.serialize(fragment);
|
| | |
|
| | | // Check if parent is empty or only has one BR element then set the innerHTML of that parent
|
| | | node = parentNode.firstChild;
|
| | | node2 = parentNode.lastChild;
|
| | | if (!node || (node === node2 && node.nodeName === 'BR'))
|
| | | dom.setHTML(parentNode, value);
|
| | | else
|
| | | selection.setContent(value);
|
| | | } else {
|
| | | // If the fragment was invalid within that context then we need
|
| | | // to parse and process the parent it's inserted into
|
| | |
|
| | | // Insert bookmark node and get the parent
|
| | | selection.setContent(bookmarkHtml);
|
| | | parentNode = editor.selection.getNode();
|
| | | rootNode = editor.getBody();
|
| | |
|
| | | // Opera will return the document node when selection is in root
|
| | | if (parentNode.nodeType == 9)
|
| | | parentNode = node = rootNode;
|
| | | else
|
| | | node = parentNode;
|
| | |
|
| | | // Find the ancestor just before the root element
|
| | | while (node !== rootNode) {
|
| | | parentNode = node;
|
| | | node = node.parentNode;
|
| | | }
|
| | |
|
| | | // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
|
| | | value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
|
| | | value = serializer.serialize(
|
| | | parser.parse(
|
| | | // Need to replace by using a function since $ in the contents would otherwise be a problem
|
| | | value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
|
| | | return serializer.serialize(fragment);
|
| | | })
|
| | | )
|
| | | );
|
| | |
|
| | | // Set the inner/outer HTML depending on if we are in the root or not
|
| | | if (parentNode == rootNode)
|
| | | dom.setHTML(rootNode, value);
|
| | | else
|
| | | dom.setOuterHTML(parentNode, value);
|
| | | }
|
| | |
|
| | | marker = dom.get('mce_marker');
|
| | |
|
| | | // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
|
| | | nodeRect = dom.getRect(marker);
|
| | | viewPortRect = dom.getViewPort(editor.getWin());
|
| | |
|
| | | // Check if node is out side the viewport if it is then scroll to it
|
| | | if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
|
| | | (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
|
| | | viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
|
| | | viewportBodyElement.scrollLeft = nodeRect.x;
|
| | | viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
|
| | | }
|
| | |
|
| | | // Move selection before marker and remove it
|
| | | rng = dom.createRng();
|
| | |
|
| | | // If previous sibling is a text node set the selection to the end of that node
|
| | | node = marker.previousSibling;
|
| | | if (node && node.nodeType == 3) {
|
| | | rng.setStart(node, node.nodeValue.length);
|
| | | } 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
|
| | | selection.onSetContent.dispatch(selection, args);
|
| | | editor.addVisual();
|
| | | },
|
| | |
| | | },
|
| | |
|
| | | mceToggleFormat : function(command, ui, value) {
|
| | | editor.formatter.toggle(value);
|
| | | formatter.toggle(value);
|
| | | },
|
| | |
|
| | | InsertHorizontalRule : function() {
|
| | |
| | | },
|
| | |
|
| | | mceReplaceContent : function(command, ui, value) {
|
| | | editor.execCommand('mceInsertContent', false, selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))));
|
| | | editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
|
| | | },
|
| | |
|
| | | mceInsertLink : function(command, ui, value) {
|
| | | var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
|
| | | var anchor;
|
| | |
|
| | | if (tinymce.is(value, 'string'))
|
| | | if (typeof(value) == 'string')
|
| | | value = {href : value};
|
| | |
|
| | | anchor = dom.getParent(selection.getNode(), 'a');
|
| | |
|
| | | // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
|
| | | value.href = value.href.replace(' ', '%20');
|
| | |
|
| | | if (!link) {
|
| | | // WebKit can't create links on float images for some odd reason so just remove it and restore it later
|
| | | if (tinymce.isWebKit) {
|
| | | img = dom.getParent(selection.getNode(), 'img');
|
| | | // Remove existing links if there could be child links or that the href isn't specified
|
| | | if (!anchor || !value.href) {
|
| | | formatter.remove('link');
|
| | | } |
| | |
|
| | | if (img) {
|
| | | floatVal = img.style.cssFloat;
|
| | | img.style.cssFloat = null;
|
| | | }
|
| | | }
|
| | |
|
| | | execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
|
| | |
|
| | | // Restore float value
|
| | | if (floatVal)
|
| | | img.style.cssFloat = floatVal;
|
| | |
|
| | | each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
|
| | | dom.setAttribs(link, value);
|
| | | });
|
| | | } else {
|
| | | if (value.href)
|
| | | dom.setAttribs(link, value);
|
| | | else
|
| | | editor.dom.remove(link, TRUE);
|
| | | // Apply new link to selection
|
| | | if (value.href) {
|
| | | formatter.apply('link', value, anchor);
|
| | | }
|
| | | },
|
| | | |
| | |
|
| | | selectAll : function() {
|
| | | var root = dom.getRoot(), rng = dom.createRng();
|
| | |
|
| | |
| | | var Dispatcher = tinymce.util.Dispatcher;
|
| | |
|
| | | tinymce.UndoManager = function(editor) {
|
| | | var self, index = 0, data = [];
|
| | | var self, index = 0, data = [], beforeBookmark;
|
| | |
|
| | | function getContent() {
|
| | | return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
|
| | |
| | | onRedo : new Dispatcher(self),
|
| | |
|
| | | beforeChange : function() {
|
| | | // Set before bookmark on previous level
|
| | | if (data[index])
|
| | | data[index].beforeBookmark = editor.selection.getBookmark(2, true);
|
| | | beforeBookmark = editor.selection.getBookmark(2, true);
|
| | | },
|
| | |
|
| | | add : function(level) {
|
| | |
| | | lastLevel = data[index];
|
| | | if (lastLevel && lastLevel.content == level.content)
|
| | | return null;
|
| | |
|
| | | // Set before bookmark on previous level
|
| | | if (data[index])
|
| | | data[index].beforeBookmark = beforeBookmark;
|
| | |
|
| | | // Time to compress
|
| | | if (settings.custom_undo_redo_levels) {
|
| | |
| | | return rng2.cloneContents().textContent.length == 0;
|
| | | };
|
| | |
|
| | | function isEmpty(n) {
|
| | | n = n.innerHTML;
|
| | |
|
| | | n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
|
| | | n = n.replace(/<[^>]+>/g, ''); // Remove all tags
|
| | |
|
| | | return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
|
| | | };
|
| | |
|
| | | function splitList(selection, dom, li) {
|
| | | var listBlock, block;
|
| | |
|
| | | if (isEmpty(li)) {
|
| | | if (dom.isEmpty(li)) {
|
| | | listBlock = dom.getParent(li, 'ul,ol');
|
| | |
|
| | | if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
|
| | |
| | | s.element = elm.toUpperCase();
|
| | |
|
| | | ed.onPreInit.add(t.setup, t);
|
| | |
|
| | | if (s.forced_root_block) {
|
| | | ed.onInit.add(t.forceRoots, t);
|
| | | ed.onSetContent.add(t.forceRoots, t);
|
| | | ed.onBeforeGetContent.add(t.forceRoots, t);
|
| | | ed.onExecCommand.add(function(ed, cmd) {
|
| | | if (cmd == 'mceInsertContent') {
|
| | | t.forceRoots();
|
| | | ed.nodeChanged();
|
| | | }
|
| | | });
|
| | | }
|
| | | },
|
| | |
|
| | | setup : function() {
|
| | | var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
|
| | | var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
|
| | |
|
| | | // Force root blocks when typing and when getting output
|
| | | // Force root blocks
|
| | | if (s.forced_root_block) {
|
| | | ed.onBeforeExecCommand.add(t.forceRoots, t);
|
| | | ed.onKeyUp.add(t.forceRoots, t);
|
| | | ed.onPreProcess.add(t.forceRoots, t);
|
| | | function addRootBlocks() {
|
| | | var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
|
| | |
|
| | | if (!node || node.nodeType !== 1)
|
| | | return;
|
| | |
|
| | | // Check if node is wrapped in block
|
| | | while (node != rootNode) {
|
| | | if (blockElements[node.nodeName])
|
| | | return;
|
| | |
|
| | | node = node.parentNode;
|
| | | }
|
| | |
|
| | | // Get current selection
|
| | | rng = selection.getRng();
|
| | | if (rng.setStart) {
|
| | | startContainer = rng.startContainer;
|
| | | startOffset = rng.startOffset;
|
| | | endContainer = rng.endContainer;
|
| | | endOffset = rng.endOffset;
|
| | | } else {
|
| | | // Force control range into text range
|
| | | if (rng.item) {
|
| | | rng = ed.getDoc().body.createTextRange();
|
| | | rng.moveToElementText(rng.item(0));
|
| | | }
|
| | |
|
| | | tmpRng = rng.duplicate();
|
| | | tmpRng.collapse(true);
|
| | | startOffset = tmpRng.move('character', offset) * -1;
|
| | |
|
| | | if (!tmpRng.collapsed) {
|
| | | tmpRng = rng.duplicate();
|
| | | tmpRng.collapse(false);
|
| | | endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
|
| | | }
|
| | | }
|
| | |
|
| | | // Wrap non block elements and text nodes
|
| | | for (node = rootNode.firstChild; node; node) {
|
| | | if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
|
| | | if (!rootBlockNode) {
|
| | | rootBlockNode = dom.create(s.forced_root_block);
|
| | | node.parentNode.insertBefore(rootBlockNode, node);
|
| | | }
|
| | |
|
| | | tempNode = node;
|
| | | node = node.nextSibling;
|
| | | rootBlockNode.appendChild(tempNode);
|
| | | } else {
|
| | | rootBlockNode = null;
|
| | | node = node.nextSibling;
|
| | | }
|
| | | }
|
| | |
|
| | | if (rng.setStart) {
|
| | | rng.setStart(startContainer, startOffset);
|
| | | rng.setEnd(endContainer, endOffset);
|
| | | selection.setRng(rng);
|
| | | } else {
|
| | | try {
|
| | | rng = ed.getDoc().body.createTextRange();
|
| | | rng.moveToElementText(rootNode);
|
| | | rng.collapse(true);
|
| | | rng.moveStart('character', startOffset);
|
| | |
|
| | | if (endOffset > 0)
|
| | | rng.moveEnd('character', endOffset);
|
| | |
|
| | | rng.select();
|
| | | } catch (ex) {
|
| | | // Ignore
|
| | | }
|
| | | }
|
| | |
|
| | | ed.nodeChanged();
|
| | | };
|
| | |
|
| | | ed.onKeyUp.add(addRootBlocks);
|
| | | ed.onClick.add(addRootBlocks);
|
| | | }
|
| | |
|
| | | if (s.force_br_newlines) {
|
| | |
| | | }
|
| | | },
|
| | |
|
| | | find : function(n, t, s) {
|
| | | var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
|
| | |
|
| | | while (n = w.nextNode()) {
|
| | | c++;
|
| | |
|
| | | // Index by node
|
| | | if (t == 0 && n == s)
|
| | | return c;
|
| | |
|
| | | // Node by index
|
| | | if (t == 1 && c == s)
|
| | | return n;
|
| | | }
|
| | |
|
| | | return -1;
|
| | | },
|
| | |
|
| | | forceRoots : function(ed, e) {
|
| | | var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
|
| | | var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
|
| | |
|
| | | // Fix for bug #1863847
|
| | | //if (e && e.keyCode == 13)
|
| | | // return TRUE;
|
| | |
|
| | | // Wrap non blocks into blocks
|
| | | for (i = nl.length - 1; i >= 0; i--) {
|
| | | nx = nl[i];
|
| | |
|
| | | // Ignore internal elements
|
| | | if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
|
| | | bl = null;
|
| | | continue;
|
| | | }
|
| | |
|
| | | // Is text or non block element
|
| | | if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
|
| | | if (!bl) {
|
| | | // Create new block but ignore whitespace
|
| | | if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
|
| | | // Store selection
|
| | | if (si == -2 && r) {
|
| | | if (!isIE || r.setStart) {
|
| | | // If selection is element then mark it
|
| | | if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
|
| | | // Save the id of the selected element
|
| | | eid = n.getAttribute("id");
|
| | | n.setAttribute("id", "__mce");
|
| | | } else {
|
| | | // If element is inside body, might not be the case in contentEdiable mode
|
| | | if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
|
| | | so = r.startOffset;
|
| | | eo = r.endOffset;
|
| | | si = t.find(b, 0, r.startContainer);
|
| | | ei = t.find(b, 0, r.endContainer);
|
| | | }
|
| | | }
|
| | | } else {
|
| | | // Force control range into text range
|
| | | if (r.item) {
|
| | | tr = d.body.createTextRange();
|
| | | tr.moveToElementText(r.item(0));
|
| | | r = tr;
|
| | | }
|
| | |
|
| | | tr = d.body.createTextRange();
|
| | | tr.moveToElementText(b);
|
| | | tr.collapse(1);
|
| | | bp = tr.move('character', c) * -1;
|
| | |
|
| | | tr = r.duplicate();
|
| | | tr.collapse(1);
|
| | | sp = tr.move('character', c) * -1;
|
| | |
|
| | | tr = r.duplicate();
|
| | | tr.collapse(0);
|
| | | le = (tr.move('character', c) * -1) - sp;
|
| | |
|
| | | si = sp - bp;
|
| | | ei = le;
|
| | | }
|
| | | }
|
| | |
|
| | | // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
|
| | | // See: http://support.microsoft.com/kb/829907
|
| | | bl = ed.dom.create(ed.settings.forced_root_block);
|
| | | nx.parentNode.replaceChild(bl, nx);
|
| | | bl.appendChild(nx);
|
| | | }
|
| | | } else {
|
| | | if (bl.hasChildNodes())
|
| | | bl.insertBefore(nx, bl.firstChild);
|
| | | else
|
| | | bl.appendChild(nx);
|
| | | }
|
| | | } else
|
| | | bl = null; // Time to create new block
|
| | | }
|
| | |
|
| | | // Restore selection
|
| | | if (si != -2) {
|
| | | if (!isIE || r.setStart) {
|
| | | bl = b.getElementsByTagName(ed.settings.element)[0];
|
| | | r = d.createRange();
|
| | |
|
| | | // Select last location or generated block
|
| | | if (si != -1)
|
| | | r.setStart(t.find(b, 1, si), so);
|
| | | else
|
| | | r.setStart(bl, 0);
|
| | |
|
| | | // Select last location or generated block
|
| | | if (ei != -1)
|
| | | r.setEnd(t.find(b, 1, ei), eo);
|
| | | else
|
| | | r.setEnd(bl, 0);
|
| | |
|
| | | if (s) {
|
| | | s.removeAllRanges();
|
| | | s.addRange(r);
|
| | | }
|
| | | } else {
|
| | | try {
|
| | | r = s.createRange();
|
| | | r.moveToElementText(b);
|
| | | r.collapse(1);
|
| | | r.moveStart('character', si);
|
| | | r.moveEnd('character', ei);
|
| | | r.select();
|
| | | } catch (ex) {
|
| | | // Ignore
|
| | | }
|
| | | }
|
| | | } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
|
| | | // Restore the id of the selected element
|
| | | if (eid)
|
| | | n.setAttribute('id', eid);
|
| | | else
|
| | | n.removeAttribute('id');
|
| | |
|
| | | // Move caret before selected element
|
| | | r = d.createRange();
|
| | | r.setStartBefore(n);
|
| | | r.setEndBefore(n);
|
| | | se.setRng(r);
|
| | | }
|
| | | },
|
| | |
|
| | | getParentBlock : function(n) {
|
| | | var d = this.dom;
|
| | |
|
| | |
| | | rb.setStart(sn, 0);
|
| | | ra = d.createRange();
|
| | | ra.setStart(en, 0);
|
| | | }
|
| | |
|
| | | // If the body is totally empty add a BR element this might happen on webkit
|
| | | if (!d.body.hasChildNodes()) {
|
| | | d.body.appendChild(dom.create('br'));
|
| | | }
|
| | |
|
| | | // Never use body as start or end node
|
| | |
| | | if (aft.firstChild && aft.firstChild.nodeName == bn)
|
| | | aft.innerHTML = aft.firstChild.innerHTML;
|
| | |
|
| | | // Padd empty blocks
|
| | | if (isEmpty(bef))
|
| | | bef.innerHTML = '<br />';
|
| | |
|
| | | function appendStyles(e, en) {
|
| | | var nl = [], nn, n, i;
|
| | |
|
| | |
| | | } else
|
| | | e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
|
| | | };
|
| | | |
| | | // Padd empty blocks
|
| | | if (dom.isEmpty(bef))
|
| | | appendStyles(bef, sn);
|
| | |
|
| | | // Fill empty afterblook with current style
|
| | | if (isEmpty(aft))
|
| | | if (dom.isEmpty(aft))
|
| | | car = appendStyles(aft, en);
|
| | |
|
| | | // Opera needs this one backwards for older versions
|
| | |
| | | aft.normalize();
|
| | | bef.normalize();
|
| | |
|
| | | function first(n) {
|
| | | return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
|
| | | };
|
| | |
|
| | | // Move cursor and scroll into view
|
| | | r = d.createRange();
|
| | | r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
|
| | | r.collapse(1);
|
| | | s.removeAllRanges();
|
| | | s.addRange(r);
|
| | | ed.selection.select(aft, true);
|
| | | ed.selection.collapse(true);
|
| | |
|
| | | // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
|
| | | y = ed.dom.getPos(aft).y;
|
| | |
| | |
|
| | | id = t.prefix + id;
|
| | |
|
| | | if (ed.settings.use_native_selects)
|
| | |
|
| | | function useNativeListForAccessibility(ed) {
|
| | | return ed.settings.use_accessible_selects && !tinymce.isGecko
|
| | | }
|
| | |
|
| | | if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
|
| | | c = new tinymce.ui.NativeListBox(id, s);
|
| | | else {
|
| | | cls = cc || t._cls.listbox || tinymce.ui.ListBox;
|
| | |
| | | ed.onMouseDown.add(c.hideMenu, c);
|
| | | } else {
|
| | | cls = t._cls.button || tinymce.ui.Button;
|
| | | c = new cls(id, s);
|
| | | c = new cls(id, s, ed);
|
| | | }
|
| | |
|
| | | return t.add(c);
|
| | |
| | | fmt = fmt || format;
|
| | |
|
| | | if (elm) {
|
| | | if (fmt.onformat) {
|
| | | fmt.onformat(elm, fmt, vars, node);
|
| | | }
|
| | |
|
| | | each(fmt.styles, function(value, name) {
|
| | | dom.setStyle(elm, name, replaceVars(value, vars));
|
| | | });
|
| | |
| | | });
|
| | | }
|
| | | };
|
| | | function adjustSelectionToVisibleSelection() {
|
| | | function findSelectionEnd(start, end) {
|
| | | var walker = new TreeWalker(end);
|
| | | for (node = walker.current(); node; node = walker.prev()) {
|
| | | if (node.childNodes.length > 1 || node == start) {
|
| | | return node;
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | | function applyRngStyle(rng) {
|
| | | // Adjust selection so that a end container with a end offset of zero is not included in the selection
|
| | | // as this isn't visible to the user.
|
| | | var rng = ed.selection.getRng();
|
| | | var start = rng.startContainer;
|
| | | var end = rng.endContainer;
|
| | |
|
| | | if (start != end && rng.endOffset == 0) {
|
| | | var newEnd = findSelectionEnd(start, end);
|
| | | var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
|
| | |
|
| | | rng.setEnd(newEnd, endOffset);
|
| | | }
|
| | |
|
| | | 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(tinymce.grep(node.childNodes), process);
|
| | | return 0;
|
| | | } else {
|
| | | currentWrapElm = wrapElm.cloneNode(FALSE);
|
| | | |
| | | // create a list of the nodes on the same side of the list as the selection
|
| | | each(tinymce.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) {
|
| | | var newWrappers = [], wrapName, wrapElm;
|
| | |
|
| | | // Setup wrapper element
|
| | |
| | | }
|
| | |
|
| | | 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;
|
| | |
| | | }
|
| | |
|
| | | // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
|
| | | if (node) {
|
| | | if (node && format.merge_siblings !== false) {
|
| | | node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
|
| | | node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
|
| | | }
|
| | |
| | | var curSelNode = ed.selection.getNode();
|
| | |
|
| | | // Apply formatting to selection
|
| | | ed.selection.setRng(adjustSelectionToVisibleSelection());
|
| | | bookmark = selection.getBookmark();
|
| | | applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
|
| | | applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
|
| | |
|
| | | // Colored nodes should be underlined so that the color of the underline matches the text color.
|
| | | if (format.styles && (format.styles.color || format.styles.textDecoration)) {
|
| | |
| | |
|
| | | function remove(name, vars, node) {
|
| | | var formatList = get(name), format = formatList[0], bookmark, i, rng;
|
| | |
|
| | | function moveStart(rng) {
|
| | | var container = rng.startContainer,
|
| | | offset = rng.startOffset,
|
| | |
| | | selection.moveToBookmark(bookmark);
|
| | |
|
| | | // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
|
| | | if (match(name, vars, selection.getStart())) {
|
| | | if (format.inline && match(name, vars, selection.getStart())) {
|
| | | moveStart(selection.getRng(true));
|
| | | }
|
| | |
|
| | |
| | |
|
| | | function matchItems(node, format, item_name) {
|
| | | var key, value, items = format[item_name], i;
|
| | |
|
| | | // Custom match
|
| | | if (format.onmatch) {
|
| | | return format.onmatch(node, format, item_name);
|
| | | }
|
| | |
|
| | | // Check all items
|
| | | if (items) {
|
| | |
| | | // Only register listeners once if we need to
|
| | | if (!pendingFormats.isListening && hasPending()) {
|
| | | pendingFormats.isListening = true;
|
| | | function performPendingFormat(node, textNode) {
|
| | | var rng = dom.createRng();
|
| | | perform(node);
|
| | |
|
| | | rng.setStart(textNode, textNode.nodeValue.length);
|
| | | rng.setEnd(textNode, textNode.nodeValue.length);
|
| | | selection.setRng(rng);
|
| | | ed.nodeChanged();
|
| | | }
|
| | | var enterKeyPressed = false;
|
| | |
|
| | | each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
|
| | | ed[event].addToTop(function(ed, e) {
|
| | | if (e.keyCode==13 && !e.shiftKey) {
|
| | | enterKeyPressed = true;
|
| | | return;
|
| | | }
|
| | | // Do we have pending formats and is the selection moved has moved
|
| | | if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
|
| | | var foundCaret = false;
|
| | | each(dom.select('font,span'), function(node) {
|
| | | var textNode, rng;
|
| | |
|
| | | // Look for marker
|
| | | if (isCaretNode(node)) {
|
| | | foundCaret = true;
|
| | | textNode = node.firstChild;
|
| | |
|
| | | if (textNode) {
|
| | | perform(node);
|
| | | // Find the first text node within node
|
| | | while (textNode && textNode.nodeType != 3)
|
| | | textNode = textNode.firstChild;
|
| | |
|
| | | rng = dom.createRng();
|
| | | rng.setStart(textNode, textNode.nodeValue.length);
|
| | | rng.setEnd(textNode, textNode.nodeValue.length);
|
| | | selection.setRng(rng);
|
| | | ed.nodeChanged();
|
| | | } else
|
| | | if (textNode) |
| | | performPendingFormat(node, textNode);
|
| | | else
|
| | | dom.remove(node);
|
| | | }
|
| | | });
|
| | | |
| | | // no caret - so we are |
| | | if (enterKeyPressed && !foundCaret) {
|
| | | var node = selection.getNode();
|
| | | var textNode = node;
|
| | |
|
| | | // Find the first text node within node
|
| | | while (textNode && textNode.nodeType != 3)
|
| | | textNode = textNode.firstChild;
|
| | | if (textNode) {
|
| | | node=textNode.parentNode;
|
| | | while (!isBlock(node)){
|
| | | node=node.parentNode;
|
| | | }
|
| | | performPendingFormat(node, textNode);
|
| | | }
|
| | | }
|
| | |
|
| | | // Always unbind and clear pending styles on keyup
|
| | | if (e.type == 'keyup' || e.type == 'mouseup')
|
| | | if (e.type == 'keyup' || e.type == 'mouseup') {
|
| | | resetPending();
|
| | | enterKeyPressed=false;
|
| | | }
|
| | | }
|
| | | });
|
| | | });
|