comparison media/js/tiny_mce/tiny_mce_src.js @ 217:237710206167

Update TinyMCE to 3.3.6
author Brian Neal <bgneal@gmail.com>
date Tue, 01 Jun 2010 04:49:29 +0000
parents 149c3567fec1
children 6ed2932901fa
comparison
equal deleted inserted replaced
216:fe900598f81c 217:237710206167
3 undefined; 3 undefined;
4 4
5 var tinymce = { 5 var tinymce = {
6 majorVersion : '3', 6 majorVersion : '3',
7 7
8 minorVersion : '3.2', 8 minorVersion : '3.6',
9 9
10 releaseDate : '2010-03-25', 10 releaseDate : '2010-05-20',
11 11
12 _init : function() { 12 _init : function() {
13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
14 14
15 t.isOpera = win.opera && opera.buildNumber; 15 t.isOpera = win.opera && opera.buildNumber;
23 t.isGecko = !t.isWebKit && /Gecko/.test(ua); 23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
24 24
25 t.isMac = ua.indexOf('Mac') != -1; 25 t.isMac = ua.indexOf('Mac') != -1;
26 26
27 t.isAir = /adobeair/i.test(ua); 27 t.isAir = /adobeair/i.test(ua);
28
29 t.isIDevice = /(iPad|iPhone)/.test(ua);
28 30
29 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 31 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
30 if (win.tinyMCEPreInit) { 32 if (win.tinyMCEPreInit) {
31 t.suffix = tinyMCEPreInit.suffix; 33 t.suffix = tinyMCEPreInit.suffix;
32 t.baseURL = tinyMCEPreInit.base; 34 t.baseURL = tinyMCEPreInit.base;
1260 return null; 1262 return null;
1261 1263
1262 if (keep_children) { 1264 if (keep_children) {
1263 while (child = node.firstChild) { 1265 while (child = node.firstChild) {
1264 // IE 8 will crash if you don't remove completely empty text nodes 1266 // IE 8 will crash if you don't remove completely empty text nodes
1265 if (child.nodeType !== 3 || child.nodeValue) 1267 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
1266 parent.insertBefore(child, node); 1268 parent.insertBefore(child, node);
1267 else 1269 else
1268 node.removeChild(child); 1270 node.removeChild(child);
1269 } 1271 }
1270 } 1272 }
1906 // Time to fix the madness IE left us 1908 // Time to fix the madness IE left us
1907 if (x) { 1909 if (x) {
1908 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs 1910 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
1909 // after we use innerHTML we can fix the DOM tree 1911 // after we use innerHTML we can fix the DOM tree
1910 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">'); 1912 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
1911 h = h.replace(/<\/p>/g, '</div>'); 1913 h = h.replace(/<\/p>/gi, '</div>');
1912 1914
1913 // Set the new HTML with DIVs 1915 // Set the new HTML with DIVs
1914 set(); 1916 set();
1915 1917
1916 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs 1918 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
2410 2412
2411 if (node) { 2413 if (node) {
2412 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 2414 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
2413 nodeType = node.nodeType; 2415 nodeType = node.nodeType;
2414 2416
2415 // Handle normalization of text nodes 2417 // Normalize text nodes
2416 if (!normalized || nodeType != 3 || (lastNodeType != nodeType && node.nodeValue.length)) 2418 if (normalized && nodeType == 3) {
2417 idx++; 2419 if (nodeType == lastNodeType || !node.nodeValue.length)
2418 2420 continue;
2421 }
2422
2423 idx++;
2419 lastNodeType = nodeType; 2424 lastNodeType = nodeType;
2420 } 2425 }
2421 } 2426 }
2422 2427
2423 return idx; 2428 return idx;
3253 3258
3254 (function() { 3259 (function() {
3255 function Selection(selection) { 3260 function Selection(selection) {
3256 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false; 3261 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
3257 3262
3258 // Compares two IE specific ranges to see if they are different
3259 // this method is useful when invalidating the cached selection range
3260 function compareRanges(rng1, rng2) {
3261 if (rng1 && rng2) {
3262 // Both are control ranges and the selected element matches
3263 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
3264 return TRUE;
3265
3266 // Both are text ranges and the range matches
3267 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
3268 // IE will say that the range is equal then produce an invalid argument exception
3269 // if you perform specific operations in a keyup event. For example Ctrl+Del.
3270 // This hack will invalidate the range cache if the exception occurs
3271 try {
3272 // Try accessing nextSibling will producer an invalid argument some times
3273 range.startContainer.nextSibling;
3274 return TRUE;
3275 } catch (ex) {
3276 // Ignore
3277 }
3278 }
3279 }
3280
3281 return FALSE;
3282 };
3283
3284 // Returns a W3C DOM compatible range object by using the IE Range API 3263 // Returns a W3C DOM compatible range object by using the IE Range API
3285 function getRange() { 3264 function getRange() {
3286 var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged; 3265 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
3287 3266
3288 // If selection is outside the current document just return an empty range 3267 // If selection is outside the current document just return an empty range
3289 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 3268 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
3290 if (element.ownerDocument != dom.doc) 3269 if (element.ownerDocument != dom.doc)
3291 return domRange; 3270 return domRange;
3296 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 3275 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
3297 3276
3298 return domRange; 3277 return domRange;
3299 } 3278 }
3300 3279
3301 // Duplicare IE selection range and check if the range is collapsed
3302 ieRange2 = ieRange.duplicate();
3303 collapsed = selection.isCollapsed(); 3280 collapsed = selection.isCollapsed();
3304 3281
3305 // Insert invisible start marker 3282 function findEndPoint(start) {
3306 ieRange.collapse(); 3283 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
3307 ieRange.pasteHTML('<span id="_mce_start" style="display:none;line-height:0">' + invisibleChar + '</span>'); 3284
3308 3285 // Setup temp range and collapse it
3309 // Insert invisible end marker 3286 checkRng = ieRange.duplicate();
3310 if (!collapsed) { 3287 checkRng.collapse(start);
3311 ieRange2.collapse(FALSE); 3288
3312 ieRange2.pasteHTML('<span id="_mce_end" style="display:none;line-height:0">' + invisibleChar + '</span>'); 3289 // Create marker and insert it at the end of the endpoints parent
3313 } 3290 marker = dom.create('a');
3314 3291 parent = checkRng.parentElement();
3315 // Sets the end point of the range by looking for the marker 3292 parent.appendChild(marker);
3316 // This method also merges the text nodes it splits so that 3293 checkRng.moveToElementText(marker);
3317 // the DOM doesn't get fragmented. 3294 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
3318 function setEndPoint(start) { 3295 if (position > 0) {
3319 var container, offset, marker, sibling; 3296 // The position is after the end of the parent element.
3320 3297 // This is the case where IE puts the caret to the left edge of a table.
3321 // Look for endpoint marker 3298 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
3322 marker = dom.get('_mce_' + (start ? 'start' : 'end'));
3323 sibling = marker.previousSibling;
3324
3325 // Is marker after a text node
3326 if (sibling && sibling.nodeType == 3) {
3327 // Get container node and calc offset
3328 container = sibling;
3329 offset = container.nodeValue.length;
3330 dom.remove(marker); 3299 dom.remove(marker);
3331 3300 return;
3332 // Merge text nodes to reduce DOM fragmentation 3301 }
3333 sibling = container.nextSibling; 3302
3334 if (sibling && sibling.nodeType == 3) { 3303 // Setup node list and endIndex
3335 isMerged = TRUE; 3304 nodes = tinymce.grep(parent.childNodes);
3336 container.appendData(sibling.nodeValue); 3305 endIndex = nodes.length - 1;
3337 dom.remove(sibling); 3306 // Perform a binary search for the position
3307 while (startIndex <= endIndex) {
3308 index = Math.floor((startIndex + endIndex) / 2);
3309
3310 // Insert marker and check it's position relative to the selection
3311 parent.insertBefore(marker, nodes[index]);
3312 checkRng.moveToElementText(marker);
3313 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
3314 if (position > 0) {
3315 // Marker is to the right
3316 startIndex = index + 1;
3317 } else if (position < 0) {
3318 // Marker is to the left
3319 endIndex = index - 1;
3320 } else {
3321 // Maker is where we are
3322 found = true;
3323 break;
3338 } 3324 }
3325 }
3326
3327 // Setup container
3328 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
3329
3330 // Handle element selection
3331 if (container.nodeType == 1) {
3332 dom.remove(marker);
3333
3334 // Find offset and container
3335 offset = dom.nodeIndex(container);
3336 container = container.parentNode;
3337
3338 // Move the offset if we are setting the end or the position is after an element
3339 if (!start || index > 0)
3340 offset++;
3339 } else { 3341 } else {
3340 sibling = marker.nextSibling; 3342 // Calculate offset within text node
3341 3343 if (position > 0 || index == 0) {
3342 // Is marker before a text node 3344 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
3343 if (sibling && sibling.nodeType == 3) { 3345 offset = checkRng.text.length;
3344 container = sibling;
3345 offset = 0;
3346 } else { 3346 } else {
3347 // Is marker before an element 3347 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
3348 if (sibling) 3348 offset = container.nodeValue.length - checkRng.text.length;
3349 offset = dom.nodeIndex(sibling) - 1;
3350 else
3351 offset = dom.nodeIndex(marker);
3352
3353 container = marker.parentNode;
3354 } 3349 }
3355 3350
3356 dom.remove(marker); 3351 dom.remove(marker);
3357 } 3352 }
3358 3353
3359 // Set start of range 3354 domRange[start ? 'setStart' : 'setEnd'](container, offset);
3360 if (start)
3361 domRange.setStart(container, offset);
3362
3363 // Set end of range or automatically if it's collapsed to increase performance
3364 if (!start || collapsed)
3365 domRange.setEnd(container, offset);
3366 }; 3355 };
3367 3356
3368 // Set start of range 3357 // Find start point
3369 setEndPoint(TRUE); 3358 findEndPoint(true);
3370 3359
3371 // Set end of range if needed 3360 // Find end point if needed
3372 if (!collapsed) 3361 if (!collapsed)
3373 setEndPoint(FALSE); 3362 findEndPoint();
3374
3375 // Restore selection if the range contents was merged
3376 // since the selection was then moved since the text nodes got changed
3377 if (isMerged)
3378 t.addRange(domRange);
3379 3363
3380 return domRange; 3364 return domRange;
3381 }; 3365 };
3382 3366
3383 this.addRange = function(rng) { 3367 this.addRange = function(rng) {
3487 ieRng.collapse(FALSE); 3471 ieRng.collapse(FALSE);
3488 } 3472 }
3489 3473
3490 // If same text container then we can do a more simple move 3474 // If same text container then we can do a more simple move
3491 if (sc == ec && sc.nodeType == 3) { 3475 if (sc == ec && sc.nodeType == 3) {
3492 ieRng.moveEnd('character', eo - so); 3476 try {
3493 ieRng.select(); 3477 ieRng.moveEnd('character', eo - so);
3494 ieRng.scrollIntoView(); 3478 ieRng.select();
3479 ieRng.scrollIntoView();
3480 } catch (ex) {
3481 // Some times a Runtime error of the 800a025e type gets thrown
3482 // especially when the caret is placed before a table.
3483 // This is a somewhat strange location for the caret.
3484 // TODO: Find a better solution for this would possible require a rewrite of the setRng method
3485 }
3486
3495 return; 3487 return;
3496 } 3488 }
3497 3489
3498 // Set end of range to endContainer/endOffset 3490 // Set end of range to endContainer/endOffset
3499 ieRng2 = body.createTextRange(); 3491 ieRng2 = body.createTextRange();
3516 ieRng.scrollIntoView(); 3508 ieRng.scrollIntoView();
3517 }; 3509 };
3518 3510
3519 this.getRangeAt = function() { 3511 this.getRangeAt = function() {
3520 // Setup new range if the cache is empty 3512 // Setup new range if the cache is empty
3521 if (!range || !compareRanges(lastIERng, selection.getRng())) { 3513 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
3522 range = getRange(); 3514 range = getRange();
3523 3515
3524 // Store away text range for next call 3516 // Store away text range for next call
3525 lastIERng = selection.getRng(); 3517 lastIERng = selection.getRng();
3518 }
3519
3520 // IE will say that the range is equal then produce an invalid argument exception
3521 // if you perform specific operations in a keyup event. For example Ctrl+Del.
3522 // This hack will invalidate the range cache if the exception occurs
3523 try {
3524 range.startContainer.nextSibling;
3525 } catch (ex) {
3526 range = getRange();
3527 lastIERng = null;
3526 } 3528 }
3527 3529
3528 // Return cached range 3530 // Return cached range
3529 return range; 3531 return range;
3530 }; 3532 };
5064 if (r.insertNode) { 5066 if (r.insertNode) {
5065 // Make caret marker since insertNode places the caret in the beginning of text after insert 5067 // Make caret marker since insertNode places the caret in the beginning of text after insert
5066 h += '<span id="__caret">_</span>'; 5068 h += '<span id="__caret">_</span>';
5067 5069
5068 // Delete and insert new node 5070 // Delete and insert new node
5069 if (r.startContainer == d && r.endContainer == d) { 5071
5072 if (r.startContainer == d && r.endContainer == d) {
5070 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 5073 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
5071 d.body.innerHTML = h; 5074 d.body.innerHTML = h;
5072 } else { 5075 } else {
5073 r.deleteContents(); 5076 r.deleteContents();
5074 r.insertNode(t.getRng().createContextualFragment(h)); 5077 if (d.body.childNodes.length == 0) {
5078 d.body.innerHTML = h;
5079 } else {
5080 r.insertNode(r.createContextualFragment(h));
5081 }
5075 } 5082 }
5076 5083
5077 // Move to caret marker 5084 // Move to caret marker
5078 c = t.dom.get('__caret'); 5085 c = t.dom.get('__caret');
5079
5080 // Make sure we wrap it compleatly, Opera fails with a simple select call 5086 // Make sure we wrap it compleatly, Opera fails with a simple select call
5081 r = d.createRange(); 5087 r = d.createRange();
5082 r.setStartBefore(c); 5088 r.setStartBefore(c);
5083 r.setEndBefore(c); 5089 r.setEndBefore(c);
5084 t.setRng(r); 5090 t.setRng(r);
5185 } 5191 }
5186 5192
5187 point.push(offset); 5193 point.push(offset);
5188 } else { 5194 } else {
5189 childNodes = container.childNodes; 5195 childNodes = container.childNodes;
5190 5196
5191 if (offset >= childNodes.length) { 5197 if (offset >= childNodes.length && childNodes.length) {
5192 after = 1; 5198 after = 1;
5193 offset = childNodes.length - 1; 5199 offset = Math.max(0, childNodes.length - 1);
5194 } 5200 }
5195 5201
5196 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 5202 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
5197 } 5203 }
5198 5204
5267 5273
5268 return {id : id}; 5274 return {id : id};
5269 }, 5275 },
5270 5276
5271 moveToBookmark : function(bookmark) { 5277 moveToBookmark : function(bookmark) {
5272 var t = this, dom = t.dom, marker1, marker2, rng, root; 5278 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
5273 5279
5274 // Clear selection cache 5280 // Clear selection cache
5275 if (t.tridentSel) 5281 if (t.tridentSel)
5276 t.tridentSel.destroy(); 5282 t.tridentSel.destroy();
5277 5283
5299 setEndPoint(true); 5305 setEndPoint(true);
5300 setEndPoint(); 5306 setEndPoint();
5301 5307
5302 t.setRng(rng); 5308 t.setRng(rng);
5303 } else if (bookmark.id) { 5309 } else if (bookmark.id) {
5304 rng = dom.createRng();
5305
5306 function restoreEndPoint(suffix) { 5310 function restoreEndPoint(suffix) {
5307 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 5311 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
5308 5312
5309 if (marker) { 5313 if (marker) {
5310 node = marker.parentNode; 5314 node = marker.parentNode;
5315 } else { 5319 } else {
5316 node = marker; 5320 node = marker;
5317 idx = 1; 5321 idx = 1;
5318 } 5322 }
5319 5323
5320 rng.setStart(node, idx); 5324 startContainer = endContainer = node;
5321 rng.setEnd(node, idx); 5325 startOffset = endOffset = idx;
5322 } else { 5326 } else {
5323 if (!keep) { 5327 if (!keep) {
5324 idx = dom.nodeIndex(marker); 5328 idx = dom.nodeIndex(marker);
5325 } else { 5329 } else {
5326 node = marker; 5330 node = marker;
5327 idx = 1; 5331 idx = 1;
5328 } 5332 }
5329 5333
5330 rng.setEnd(node, idx); 5334 endContainer = node;
5335 endOffset = idx;
5331 } 5336 }
5332 5337
5333 if (!keep) { 5338 if (!keep) {
5334 prev = marker.previousSibling; 5339 prev = marker.previousSibling;
5335 next = marker.nextSibling; 5340 next = marker.nextSibling;
5350 idx = prev.nodeValue.length; 5355 idx = prev.nodeValue.length;
5351 prev.appendData(next.nodeValue); 5356 prev.appendData(next.nodeValue);
5352 dom.remove(next); 5357 dom.remove(next);
5353 5358
5354 if (suffix == 'start') { 5359 if (suffix == 'start') {
5355 rng.setStart(prev, idx); 5360 startContainer = endContainer = prev;
5356 rng.setEnd(prev, idx); 5361 startOffset = endOffset = idx;
5357 } else 5362 } else {
5358 rng.setEnd(prev, idx); 5363 endContainer = prev;
5364 endOffset = idx;
5365 }
5359 } 5366 }
5360 } 5367 }
5361 } 5368 }
5362 }; 5369 };
5363 5370
5364 // Restore start/end points 5371 // Restore start/end points
5365 restoreEndPoint('start'); 5372 restoreEndPoint('start');
5366 restoreEndPoint('end'); 5373 restoreEndPoint('end');
5367 5374
5375 rng = dom.createRng();
5376 rng.setStart(startContainer, startOffset);
5377 rng.setEnd(endContainer, endOffset);
5368 t.setRng(rng); 5378 t.setRng(rng);
5369 } else if (bookmark.name) { 5379 } else if (bookmark.name) {
5370 t.select(dom.select(bookmark.name)[bookmark.index]); 5380 t.select(dom.select(bookmark.name)[bookmark.index]);
5371 } else if (bookmark.rng) 5381 } else if (bookmark.rng)
5372 t.setRng(bookmark.rng); 5382 t.setRng(bookmark.rng);
5467 // This can occur when the editor is placed in a hidden container element on Gecko 5477 // This can occur when the editor is placed in a hidden container element on Gecko
5468 // Or on IE when there was an exception 5478 // Or on IE when there was an exception
5469 if (!r) 5479 if (!r)
5470 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange(); 5480 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
5471 5481
5482 if (t.selectedRange && t.explicitRange) {
5483 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
5484 // Safari, Opera and Chrome only ever select text which causes the range to change.
5485 // This lets us use the originally set range if the selection hasn't been changed by the user.
5486 r = t.explicitRange;
5487 } else {
5488 t.selectedRange = null;
5489 t.explicitRange = null;
5490 }
5491 }
5472 return r; 5492 return r;
5473 }, 5493 },
5474 5494
5475 setRng : function(r) { 5495 setRng : function(r) {
5476 var s, t = this; 5496 var s, t = this;
5477 5497
5478 if (!t.tridentSel) { 5498 if (!t.tridentSel) {
5479 s = t.getSel(); 5499 s = t.getSel();
5480 5500
5481 if (s) { 5501 if (s) {
5502 t.explicitRange = r;
5482 s.removeAllRanges(); 5503 s.removeAllRanges();
5483 s.addRange(r); 5504 s.addRange(r);
5505 t.selectedRange = s.getRangeAt(0);
5484 } 5506 }
5485 } else { 5507 } else {
5486 // Is W3C Range 5508 // Is W3C Range
5487 if (r.cloneRange) { 5509 if (r.cloneRange) {
5488 t.tridentSel.addRange(r); 5510 t.tridentSel.addRange(r);
7208 endOffset : endOffset 7230 endOffset : endOffset
7209 }; 7231 };
7210 }; 7232 };
7211 */ 7233 */
7212 }; 7234 };
7235
7236 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
7237 if (rng1 && rng2) {
7238 // Compare native IE ranges
7239 if (rng1.item || rng1.duplicate) {
7240 // Both are control ranges and the selected element matches
7241 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
7242 return true;
7243
7244 // Both are text ranges and the range matches
7245 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
7246 return true;
7247 } else {
7248 // Compare w3c ranges
7249 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
7250 }
7251 }
7252
7253 return false;
7254 };
7213 })(tinymce); 7255 })(tinymce);
7214 7256
7215 (function(tinymce) { 7257 (function(tinymce) {
7216 // Shorten class names 7258 // Shorten class names
7217 var DOM = tinymce.DOM, is = tinymce.is; 7259 var DOM = tinymce.DOM, is = tinymce.is;
9155 9197
9156 // Element not found, then skip initialization 9198 // Element not found, then skip initialization
9157 if (!t.getElement()) 9199 if (!t.getElement())
9158 return; 9200 return;
9159 9201
9202 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
9203 // browser says it has contentEditable support but there is no visible caret
9204 // We will remove this check ones Apple implements full contentEditable support
9205 if (tinymce.isIDevice)
9206 return;
9207
9160 // Add hidden input for non input elements inside form elements 9208 // Add hidden input for non input elements inside form elements
9161 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 9209 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
9162 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 9210 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
9163 9211
9164 if (tinymce.WindowManager) 9212 if (tinymce.WindowManager)
9520 9568
9521 forecolor : {inline : 'span', styles : {color : '%value'}}, 9569 forecolor : {inline : 'span', styles : {color : '%value'}},
9522 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, 9570 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
9523 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 9571 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
9524 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 9572 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
9573 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
9525 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 9574 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
9526 9575
9527 removeformat : [ 9576 removeformat : [
9528 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 9577 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
9529 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 9578 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
9798 e = null; 9847 e = null;
9799 }, 9848 },
9800 9849
9801 9850
9802 focus : function(sf) { 9851 focus : function(sf) {
9803 var oed, t = this, ce = t.settings.content_editable; 9852 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
9804 9853
9805 if (!sf) { 9854 if (!sf) {
9855 // Get selected control element
9856 ieRng = t.selection.getRng();
9857 if (ieRng.item) {
9858 controlElm = ieRng.item(0);
9859 }
9860
9806 // Is not content editable 9861 // Is not content editable
9807 if (!ce) 9862 if (!ce)
9808 t.getWin().focus(); 9863 t.getWin().focus();
9864
9865 // Restore selected control element
9866 // This is needed when for example an image is selected within a
9867 // layer a call to focus will then remove the control selection
9868 if (controlElm && controlElm.ownerDocument == doc) {
9869 ieRng = doc.body.createControlRange();
9870 ieRng.addElement(controlElm);
9871 ieRng.select();
9872 }
9809 9873
9810 } 9874 }
9811 9875
9812 if (tinymce.activeEditor != t) { 9876 if (tinymce.activeEditor != t) {
9813 if ((oed = tinymce.activeEditor) != null) 9877 if ((oed = tinymce.activeEditor) != null)
10591 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); 10655 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
10592 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); 10656 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
10593 } 10657 }
10594 10658
10595 // Add default shortcuts for gecko 10659 // Add default shortcuts for gecko
10596 if (isGecko) { 10660 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
10597 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); 10661 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
10598 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); 10662 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
10599 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
10600 }
10601 10663
10602 // BlockFormat shortcuts keys 10664 // BlockFormat shortcuts keys
10603 for (i=1; i<=6; i++) 10665 for (i=1; i<=6; i++)
10604 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 10666 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
10605 10667
10942 // Command failed 11004 // Command failed
10943 failed = TRUE; 11005 failed = TRUE;
10944 } 11006 }
10945 11007
10946 // Present alert message about clipboard access not being available 11008 // Present alert message about clipboard access not being available
10947 if (failed || !doc.queryCommandEnabled(command)) { 11009 if (failed || !doc.queryCommandSupported(command)) {
10948 if (tinymce.isGecko) { 11010 if (tinymce.isGecko) {
10949 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 11011 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
10950 if (state) 11012 if (state)
10951 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 11013 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
10952 }); 11014 });
11046 11108
11047 mceRemoveNode : function(command, ui, value) { 11109 mceRemoveNode : function(command, ui, value) {
11048 var node = value || selection.getNode(); 11110 var node = value || selection.getNode();
11049 11111
11050 // Make sure that the body node isn't removed 11112 // Make sure that the body node isn't removed
11051 if (node != ed.getBody()) { 11113 if (node != editor.getBody()) {
11052 storeSelection(); 11114 storeSelection();
11053 editor.dom.remove(node, TRUE); 11115 editor.dom.remove(node, TRUE);
11054 restoreSelection(); 11116 restoreSelection();
11055 } 11117 }
11056 }, 11118 },
11151 }); 11213 });
11152 } else { 11214 } else {
11153 if (value.href) 11215 if (value.href)
11154 dom.setAttribs(link, value); 11216 dom.setAttribs(link, value);
11155 else 11217 else
11156 ed.dom.remove(link, TRUE); 11218 editor.dom.remove(link, TRUE);
11157 } 11219 }
11220 },
11221
11222 selectAll : function() {
11223 var root = dom.getRoot();
11224 var rng = dom.createRng();
11225 rng.setStart(root, 0);
11226 rng.setEnd(root, root.childNodes.length);
11227 editor.selection.setRng(rng);
11158 } 11228 }
11159 }); 11229 });
11160 11230
11161 // Add queryCommandState overrides 11231 // Add queryCommandState overrides
11162 addCommands({ 11232 addCommands({
11497 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. 11567 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
11498 ed.getWin().scrollTo(0, divYPos); 11568 ed.getWin().scrollTo(0, divYPos);
11499 }; 11569 };
11500 11570
11501 ed.onKeyPress.add(function(ed, e) { 11571 ed.onKeyPress.add(function(ed, e) {
11502 if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) { 11572 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
11503 insertBr(ed); 11573 insertBr(ed);
11504 Event.cancel(e); 11574 Event.cancel(e);
11505 } 11575 }
11506 }); 11576 });
11507 } 11577 }
11607 si = t.find(b, 0, r.startContainer); 11677 si = t.find(b, 0, r.startContainer);
11608 ei = t.find(b, 0, r.endContainer); 11678 ei = t.find(b, 0, r.endContainer);
11609 } 11679 }
11610 } 11680 }
11611 } else { 11681 } else {
11682 // Force control range into text range
11683 if (r.item) {
11684 tr = d.body.createTextRange();
11685 tr.moveToElementText(r.item(0));
11686 r = tr;
11687 }
11688
11612 tr = d.body.createTextRange(); 11689 tr = d.body.createTextRange();
11613 tr.moveToElementText(b); 11690 tr.moveToElementText(b);
11614 tr.collapse(1); 11691 tr.collapse(1);
11615 bp = tr.move('character', c) * -1; 11692 bp = tr.move('character', c) * -1;
11616 11693
12628 offset = rng.startOffset, 12705 offset = rng.startOffset,
12629 walker, node; 12706 walker, node;
12630 12707
12631 // Move startContainer/startOffset in to a suitable node 12708 // Move startContainer/startOffset in to a suitable node
12632 if (container.nodeType == 1 || container.nodeValue === "") { 12709 if (container.nodeType == 1 || container.nodeValue === "") {
12633 walker = new TreeWalker(container.childNodes[offset]); 12710 container = container.nodeType == 1 ? container.childNodes[offset] : container;
12711 walker = new TreeWalker(container, container.parentNode);
12634 for (node = walker.current(); node; node = walker.next()) { 12712 for (node = walker.current(); node; node = walker.next()) {
12635 if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) { 12713 if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) {
12636 rng.setStart(node, 0); 12714 rng.setStart(node, 0);
12637 break; 12715 break;
12638 } 12716 }
12711 setElementFormat(node, format); 12789 setElementFormat(node, format);
12712 found = true; 12790 found = true;
12713 } 12791 }
12714 }); 12792 });
12715 12793
12716 // Contine processing if a selector match wasn't found and a inline element is defined 12794 // Continue processing if a selector match wasn't found and a inline element is defined
12717 if (!format.inline || found) { 12795 if (!format.inline || found) {
12718 currentWrapElm = 0; 12796 currentWrapElm = 0;
12719 return; 12797 return;
12720 } 12798 }
12721 } 12799 }
12804 each(dom.select(format.inline, node), function(child) { 12882 each(dom.select(format.inline, node), function(child) {
12805 removeFormat(format, vars, child, format.exact ? child : null); 12883 removeFormat(format, vars, child, format.exact ? child : null);
12806 }); 12884 });
12807 }); 12885 });
12808 12886
12887 // Remove child if direct parent is of same type
12888 if (matchNode(node.parentNode, name, vars)) {
12889 dom.remove(node, 1);
12890 node = 0;
12891 return TRUE;
12892 }
12893
12809 // Look for parent with similar style format 12894 // Look for parent with similar style format
12810 dom.getParent(node.parentNode, function(parent) { 12895 if (format.merge_with_parents) {
12811 if (matchNode(parent, name, vars)) { 12896 dom.getParent(node.parentNode, function(parent) {
12812 dom.remove(node, 1); 12897 if (matchNode(parent, name, vars)) {
12813 node = 0; 12898 dom.remove(node, 1);
12814 return TRUE; 12899 node = 0;
12815 } 12900 return TRUE;
12816 }); 12901 }
12902 });
12903 }
12817 12904
12818 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 12905 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
12819 if (node) { 12906 if (node) {
12820 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 12907 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
12821 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 12908 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
12938 13025
12939 function unwrap(start) { 13026 function unwrap(start) {
12940 var node = dom.get(start ? '_start' : '_end'), 13027 var node = dom.get(start ? '_start' : '_end'),
12941 out = node[start ? 'firstChild' : 'lastChild']; 13028 out = node[start ? 'firstChild' : 'lastChild'];
12942 13029
12943 dom.remove(node, 1); 13030 // If the end is placed within the start the result will be removed
13031 // So this checks if the out node is a bookmark node if it is it
13032 // checks for another more suitable node
13033 if (isBookmarkNode(out))
13034 out = out[start ? 'firstChild' : 'lastChild'];
13035
13036 dom.remove(node, true);
12944 13037
12945 return out; 13038 return out;
12946 }; 13039 };
12947 13040
12948 function removeRngStyle(rng) { 13041 function removeRngStyle(rng) {
13007 remove(name, vars, node); 13100 remove(name, vars, node);
13008 else 13101 else
13009 apply(name, vars, node); 13102 apply(name, vars, node);
13010 }; 13103 };
13011 13104
13012 function matchNode(node, name, vars) { 13105 function matchNode(node, name, vars, similar) {
13013 var formatList = get(name), format, i, classes; 13106 var formatList = get(name), format, i, classes;
13014 13107
13015 function matchItems(node, format, item_name) { 13108 function matchItems(node, format, item_name) {
13016 var key, value, items = format[item_name], i; 13109 var key, value, items = format[item_name], i;
13017 13110
13024 if (item_name === 'attributes') 13117 if (item_name === 'attributes')
13025 value = dom.getAttrib(node, key); 13118 value = dom.getAttrib(node, key);
13026 else 13119 else
13027 value = getStyle(node, key); 13120 value = getStyle(node, key);
13028 13121
13029 if (!isEq(value, replaceVars(items[key], vars))) 13122 if (similar && !value && !format.exact)
13123 return;
13124
13125 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
13030 return; 13126 return;
13031 } 13127 }
13032 } 13128 }
13033 } else { 13129 } else {
13034 // Only one match needed for indexed arrays 13130 // Only one match needed for indexed arrays
13067 var startNode, i; 13163 var startNode, i;
13068 13164
13069 function matchParents(node) { 13165 function matchParents(node) {
13070 // Find first node with similar format settings 13166 // Find first node with similar format settings
13071 node = dom.getParent(node, function(node) { 13167 node = dom.getParent(node, function(node) {
13072 return !!matchNode(node, name, vars); 13168 return !!matchNode(node, name, vars, true);
13073 }); 13169 });
13074 13170
13075 // Do an exact check on the similar format element 13171 // Do an exact check on the similar format element
13076 return matchNode(node, name, vars); 13172 return matchNode(node, name, vars);
13077 }; 13173 };
13689 13785
13690 return next; 13786 return next;
13691 }; 13787 };
13692 13788
13693 function isTextBlock(name) { 13789 function isTextBlock(name) {
13694 return /^(h[1-6]|p|div|pre|address)$/.test(name); 13790 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
13695 }; 13791 };
13696 13792
13697 function getContainer(rng, start) { 13793 function getContainer(rng, start) {
13698 var container, offset, lastIdx; 13794 var container, offset, lastIdx;
13699 13795
13755 } 13851 }
13756 13852
13757 // Pending apply or remove formats 13853 // Pending apply or remove formats
13758 if (hasPending()) { 13854 if (hasPending()) {
13759 ed.getDoc().execCommand('FontName', false, 'mceinline'); 13855 ed.getDoc().execCommand('FontName', false, 'mceinline');
13856 pendingFormats.lastRng = selection.getRng();
13760 13857
13761 // IE will convert the current word 13858 // IE will convert the current word
13762 each(dom.select('font,span'), function(node) { 13859 each(dom.select('font,span'), function(node) {
13763 var bookmark; 13860 var bookmark;
13764 13861
13774 if (!pendingFormats.isListening && hasPending()) { 13871 if (!pendingFormats.isListening && hasPending()) {
13775 pendingFormats.isListening = true; 13872 pendingFormats.isListening = true;
13776 13873
13777 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) { 13874 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
13778 ed[event].addToTop(function(ed, e) { 13875 ed[event].addToTop(function(ed, e) {
13779 if (hasPending()) { 13876 // Do we have pending formats and is the selection moved has moved
13877 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
13780 each(dom.select('font,span'), function(node) { 13878 each(dom.select('font,span'), function(node) {
13781 var bookmark, textNode, rng; 13879 var textNode, rng;
13782 13880
13783 // Look for marker 13881 // Look for marker
13784 if (isCaretNode(node)) { 13882 if (isCaretNode(node)) {
13785 textNode = node.firstChild; 13883 textNode = node.firstChild;
13786 13884