Mercurial > public > sg101
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 |