tinymce-rails 3.4.4.0.2 → 3.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/assets/precompiled/tinymce/langs/en.js +1 -223
  2. data/assets/precompiled/tinymce/plugins/advhr/langs/en_dlg.js +1 -7
  3. data/assets/precompiled/tinymce/plugins/advimage/js/image.js +7 -3
  4. data/assets/precompiled/tinymce/plugins/advimage/langs/en_dlg.js +1 -45
  5. data/assets/precompiled/tinymce/plugins/advlink/langs/en_dlg.js +1 -54
  6. data/assets/precompiled/tinymce/plugins/autolink/editor_plugin.js +1 -1
  7. data/assets/precompiled/tinymce/plugins/autolink/editor_plugin_src.js +1 -1
  8. data/assets/precompiled/tinymce/plugins/emotions/langs/en_dlg.js +1 -20
  9. data/assets/precompiled/tinymce/plugins/fullpage/editor_plugin.js +1 -1
  10. data/assets/precompiled/tinymce/plugins/fullpage/editor_plugin_src.js +9 -3
  11. data/assets/precompiled/tinymce/plugins/fullpage/langs/en_dlg.js +1 -85
  12. data/assets/precompiled/tinymce/plugins/fullscreen/editor_plugin.js +1 -1
  13. data/assets/precompiled/tinymce/plugins/fullscreen/editor_plugin_src.js +2 -2
  14. data/assets/precompiled/tinymce/plugins/fullscreen/fullscreen.htm +2 -1
  15. data/assets/precompiled/tinymce/plugins/lists/editor_plugin.js +1 -1
  16. data/assets/precompiled/tinymce/plugins/lists/editor_plugin_src.js +12 -0
  17. data/assets/precompiled/tinymce/plugins/media/langs/en_dlg.js +1 -112
  18. data/assets/precompiled/tinymce/plugins/media/media.htm +1 -1
  19. data/assets/precompiled/tinymce/plugins/nonbreaking/editor_plugin.js +1 -1
  20. data/assets/precompiled/tinymce/plugins/nonbreaking/editor_plugin_src.js +3 -2
  21. data/assets/precompiled/tinymce/plugins/paste/editor_plugin.js +1 -1
  22. data/assets/precompiled/tinymce/plugins/paste/editor_plugin_src.js +21 -102
  23. data/assets/precompiled/tinymce/plugins/paste/langs/en_dlg.js +1 -5
  24. data/assets/precompiled/tinymce/plugins/searchreplace/langs/en_dlg.js +1 -16
  25. data/assets/precompiled/tinymce/plugins/style/langs/en_dlg.js +1 -70
  26. data/assets/precompiled/tinymce/plugins/table/editor_plugin.js +1 -1
  27. data/assets/precompiled/tinymce/plugins/table/editor_plugin_src.js +3 -2
  28. data/assets/precompiled/tinymce/plugins/table/langs/en_dlg.js +1 -75
  29. data/assets/precompiled/tinymce/plugins/template/langs/en_dlg.js +1 -15
  30. data/assets/precompiled/tinymce/plugins/xhtmlxtras/langs/en_dlg.js +1 -32
  31. data/assets/precompiled/tinymce/themes/advanced/js/image.js +7 -3
  32. data/assets/precompiled/tinymce/themes/advanced/langs/en.js +1 -68
  33. data/assets/precompiled/tinymce/themes/advanced/langs/en_dlg.js +1 -54
  34. data/assets/precompiled/tinymce/themes/advanced/skins/default/content.css +1 -0
  35. data/assets/precompiled/tinymce/themes/advanced/skins/highcontrast/content.css +1 -0
  36. data/assets/precompiled/tinymce/themes/advanced/skins/o2k7/content.css +1 -0
  37. data/assets/precompiled/tinymce/themes/simple/langs/en.js +1 -11
  38. data/assets/precompiled/tinymce/tiny_mce.js +1 -1
  39. data/assets/precompiled/tinymce/tiny_mce_jquery.js +1 -1
  40. data/assets/precompiled/tinymce/tiny_mce_jquery_src.js +269 -171
  41. data/assets/precompiled/tinymce/tiny_mce_src.js +269 -171
  42. data/assets/precompiled/tinymce/utils/editable_selects.js +1 -1
  43. data/assets/vendor/tinymce/tiny_mce.js +269 -171
  44. data/assets/vendor/tinymce/tiny_mce_jquery.js +269 -171
  45. data/lib/tinymce/version.rb +2 -2
  46. metadata +6 -6
@@ -5,9 +5,9 @@
5
5
  var tinymce = {
6
6
  majorVersion : '3',
7
7
 
8
- minorVersion : '4.4',
8
+ minorVersion : '4.5',
9
9
 
10
- releaseDate : '2011-08-04',
10
+ releaseDate : '2011-09-06',
11
11
 
12
12
  _init : function() {
13
13
  var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
@@ -559,8 +559,9 @@ tinymce.create('tinymce.util.Dispatcher', {
559
559
  // Default settings
560
560
  s = t.settings = s || {};
561
561
 
562
- // Strange app protocol or local anchor
563
- if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
562
+ // Strange app protocol that isn't http/https or local anchor
563
+ // For example: mailto,skype,tel etc.
564
+ if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
564
565
  t.source = u;
565
566
  return;
566
567
  }
@@ -1045,31 +1046,89 @@ tinymce.create('static tinymce.util.XHR', {
1045
1046
  })(tinymce);
1046
1047
 
1047
1048
  (function(tinymce) {
1049
+ var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE;
1050
+
1048
1051
  function cleanupStylesWhenDeleting(ed) {
1049
- var dom = ed.dom, selection = ed.selection, VK= tinymce.VK;
1050
- ed.onKeyUp.add(function(ed, e) {
1051
- if (e.keyCode == VK.DELETE ||e.keyCode == VK.BACKSPACE) {
1052
- var startContainer = selection.getRng().startContainer;
1053
- var blockElement = startContainer;
1054
- while (!dom.isBlock(blockElement)) {
1055
- blockElement = blockElement.parentNode;
1052
+ var dom = ed.dom, selection = ed.selection;
1053
+
1054
+ ed.onKeyDown.add(function(ed, e) {
1055
+ var rng, blockElm, node, clonedSpan, isDelete;
1056
+
1057
+ isDelete = e.keyCode == DELETE;
1058
+ if (isDelete || e.keyCode == BACKSPACE) {
1059
+ e.preventDefault();
1060
+ rng = selection.getRng();
1061
+
1062
+ // Find root block
1063
+ blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1064
+
1065
+ // On delete clone the root span of the next block element
1066
+ if (isDelete)
1067
+ blockElm = dom.getNext(blockElm, dom.isBlock);
1068
+
1069
+ // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1070
+ if (blockElm) {
1071
+ node = blockElm.firstChild;
1072
+
1073
+ if (node && node.nodeName === 'SPAN') {
1074
+ clonedSpan = node.cloneNode(false);
1056
1075
  }
1057
- var spans = dom.select("span.Apple-style-span", blockElement);
1058
- dom.remove(spans, true);
1059
1076
  }
1060
- });
1061
- }
1062
1077
 
1078
+ // Do the backspace/delete actiopn
1079
+ ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1080
+
1081
+ // Find all odd apple-style-spans
1082
+ blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1083
+ tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1084
+ var rng = dom.createRng();
1085
+
1086
+ // Set range selection before the span we are about to remove
1087
+ rng.setStartBefore(span);
1088
+ rng.setEndBefore(span);
1089
+
1090
+ if (clonedSpan) {
1091
+ dom.replace(clonedSpan.cloneNode(false), span, true);
1092
+ } else {
1093
+ dom.remove(span, true);
1094
+ }
1095
+
1096
+ // Restore the selection
1097
+ selection.setRng(rng);
1098
+ });
1099
+ }
1100
+ });
1101
+ };
1102
+
1103
+ function emptyEditorWhenDeleting(ed) {
1104
+ ed.onKeyUp.add(function(ed, e) {
1105
+ var keyCode = e.keyCode;
1106
+
1107
+ if (keyCode == DELETE || keyCode == BACKSPACE) {
1108
+ if (ed.dom.isEmpty(ed.getBody())) {
1109
+ ed.setContent('', {format : 'raw'});
1110
+ ed.nodeChanged();
1111
+ return;
1112
+ }
1113
+ }
1114
+ });
1115
+ };
1116
+
1063
1117
  tinymce.create('tinymce.util.Quirks', {
1064
1118
  Quirks: function(ed) {
1119
+ // Load WebKit specific fixed
1065
1120
  if (tinymce.isWebKit) {
1066
1121
  cleanupStylesWhenDeleting(ed);
1122
+ emptyEditorWhenDeleting(ed);
1123
+ }
1124
+
1125
+ // Load IE specific fixes
1126
+ if (tinymce.isIE) {
1127
+ emptyEditorWhenDeleting(ed);
1067
1128
  }
1068
-
1069
1129
  }
1070
1130
  });
1071
- })(tinymce);
1072
-
1131
+ })(tinymce);
1073
1132
  (function(tinymce) {
1074
1133
  var namedEntities, baseEntities, reverseEntities,
1075
1134
  attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
@@ -1481,7 +1540,7 @@ tinymce.html.Styles = function(settings, schema) {
1481
1540
 
1482
1541
  (function(tinymce) {
1483
1542
  var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},
1484
- whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1543
+ defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1485
1544
 
1486
1545
  function split(str, delim) {
1487
1546
  return str.split(delim || ',');
@@ -1645,11 +1704,11 @@ tinymce.html.Styles = function(settings, schema) {
1645
1704
  boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls');
1646
1705
  shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1647
1706
  nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap);
1648
- whiteSpaceElementsMap = makeMap('pre,script,style,textarea');
1707
+ defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea');
1649
1708
  selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1650
1709
 
1651
1710
  tinymce.html.Schema = function(settings) {
1652
- var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1711
+ var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap;
1653
1712
 
1654
1713
  settings = settings || {};
1655
1714
 
@@ -1667,6 +1726,8 @@ tinymce.html.Styles = function(settings, schema) {
1667
1726
  });
1668
1727
  }
1669
1728
 
1729
+ whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap;
1730
+
1670
1731
  // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1671
1732
  function patternToRegExp(str) {
1672
1733
  return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
@@ -2021,9 +2082,9 @@ tinymce.html.Styles = function(settings, schema) {
2021
2082
 
2022
2083
  self.parse = function(html) {
2023
2084
  var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2024
- shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
2085
+ shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2025
2086
  validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2026
- tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
2087
+ tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2027
2088
 
2028
2089
  function processEndTag(name) {
2029
2090
  var pos, i;
@@ -2074,6 +2135,8 @@ tinymce.html.Styles = function(settings, schema) {
2074
2135
  validate = settings.validate;
2075
2136
  removeInternalElements = settings.remove_internals;
2076
2137
  fixSelfClosing = settings.fix_self_closing;
2138
+ isIE = tinymce.isIE;
2139
+ invalidPrefixRegExp = /^:/;
2077
2140
 
2078
2141
  while (matches = tokenRegExp.exec(html)) {
2079
2142
  // Text
@@ -2081,9 +2144,20 @@ tinymce.html.Styles = function(settings, schema) {
2081
2144
  self.text(decode(html.substr(index, matches.index - index)));
2082
2145
 
2083
2146
  if (value = matches[6]) { // End element
2084
- processEndTag(value.toLowerCase());
2147
+ value = value.toLowerCase();
2148
+
2149
+ // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2150
+ if (isIE && invalidPrefixRegExp.test(value))
2151
+ value = value.substr(1);
2152
+
2153
+ processEndTag(value);
2085
2154
  } else if (value = matches[7]) { // Start element
2086
2155
  value = value.toLowerCase();
2156
+
2157
+ // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2158
+ if (isIE && invalidPrefixRegExp.test(value))
2159
+ value = value.substr(1);
2160
+
2087
2161
  isShortEnded = value in shortEndedElements;
2088
2162
 
2089
2163
  // Is self closing tag for example an <li> after an open <li>
@@ -3097,11 +3171,13 @@ tinymce.html.Styles = function(settings, schema) {
3097
3171
  elementRule = schema.getElementRule(parent.name);
3098
3172
 
3099
3173
  // Remove or padd the element depending on schema rule
3100
- if (elementRule.removeEmpty)
3101
- parent.remove();
3102
- else if (elementRule.paddEmpty)
3103
- parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3104
- }
3174
+ if (elementRule) {
3175
+ if (elementRule.removeEmpty)
3176
+ parent.remove();
3177
+ else if (elementRule.paddEmpty)
3178
+ parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3179
+ }
3180
+ }
3105
3181
  }
3106
3182
  }
3107
3183
  }
@@ -4399,7 +4475,7 @@ tinymce.html.Writer = function(settings) {
4399
4475
  },
4400
4476
 
4401
4477
  isEmpty : function(node, elements) {
4402
- var self = this, i, attributes, type, walker, name;
4478
+ var self = this, i, attributes, type, walker, name, parentNode;
4403
4479
 
4404
4480
  node = node.firstChild;
4405
4481
  if (node) {
@@ -4415,8 +4491,16 @@ tinymce.html.Writer = function(settings) {
4415
4491
  continue;
4416
4492
 
4417
4493
  // Keep empty elements like <img />
4418
- if (elements && elements[node.nodeName.toLowerCase()])
4494
+ name = node.nodeName.toLowerCase();
4495
+ if (elements && elements[name]) {
4496
+ // Ignore single BR elements in blocks like <p><br /></p>
4497
+ parentNode = node.parentNode;
4498
+ if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) {
4499
+ continue;
4500
+ }
4501
+
4419
4502
  return false;
4503
+ }
4420
4504
 
4421
4505
  // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
4422
4506
  attributes = self.getAttribs(node);
@@ -7271,9 +7355,13 @@ window.tinymce.dom.Sizzle = Sizzle;
7271
7355
 
7272
7356
  if (n)
7273
7357
  e.appendChild(n);
7274
- } else if (is(r.item) || is(r.htmlText))
7275
- e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
7276
- else
7358
+ } else if (is(r.item) || is(r.htmlText)) {
7359
+ // IE will produce invalid markup if elements are present that
7360
+ // it doesn't understand like custom elements or HTML5 elements.
7361
+ // Adding a BR in front of the contents and then remoiving it seems to fix it though.
7362
+ e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
7363
+ e.removeChild(e.firstChild);
7364
+ } else
7277
7365
  e.innerHTML = r.toString();
7278
7366
 
7279
7367
  // Keep whitespace before and after
@@ -7358,7 +7446,12 @@ window.tinymce.dom.Sizzle = Sizzle;
7358
7446
  rng = self.getRng();
7359
7447
  }
7360
7448
 
7361
- rng.pasteHTML(content);
7449
+ // Explorer removes spaces from the beginning of pasted contents
7450
+ if (/^\s+/.test(content)) {
7451
+ rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
7452
+ self.dom.remove('__mce_tmp');
7453
+ } else
7454
+ rng.pasteHTML(content);
7362
7455
  }
7363
7456
 
7364
7457
  // Dispatch set content event
@@ -7965,26 +8058,29 @@ window.tinymce.dom.Sizzle = Sizzle;
7965
8058
  container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
7966
8059
  offset = 0;
7967
8060
 
7968
- // Walk the DOM to find a text node to place the caret at or a BR
7969
- node = container;
7970
- walker = new tinymce.dom.TreeWalker(container, body);
7971
- do {
7972
- // Found a text node use that position
7973
- if (node.nodeType === 3) {
7974
- offset = start ? 0 : node.nodeValue.length - 1;
7975
- container = node;
7976
- break;
7977
- }
8061
+ // Don't walk into elements that doesn't have any child nodes like a IMG
8062
+ if (container.hasChildNodes()) {
8063
+ // Walk the DOM to find a text node to place the caret at or a BR
8064
+ node = container;
8065
+ walker = new tinymce.dom.TreeWalker(container, body);
8066
+ do {
8067
+ // Found a text node use that position
8068
+ if (node.nodeType === 3) {
8069
+ offset = start ? 0 : node.nodeValue.length - 1;
8070
+ container = node;
8071
+ break;
8072
+ }
7978
8073
 
7979
- // Found a BR element that we can place the caret before
7980
- if (node.nodeName === 'BR') {
7981
- offset = dom.nodeIndex(node);
7982
- container = node.parentNode;
7983
- break;
7984
- }
7985
- } while (node = (start ? walker.next() : walker.prev()));
8074
+ // Found a BR element that we can place the caret before
8075
+ if (node.nodeName === 'BR') {
8076
+ offset = dom.nodeIndex(node);
8077
+ container = node.parentNode;
8078
+ break;
8079
+ }
8080
+ } while (node = (start ? walker.next() : walker.prev()));
7986
8081
 
7987
- normalized = true;
8082
+ normalized = true;
8083
+ }
7988
8084
  }
7989
8085
  }
7990
8086
 
@@ -8278,7 +8374,7 @@ window.tinymce.dom.Sizzle = Sizzle;
8278
8374
 
8279
8375
  // Explorer won't clone contents of script and style and the
8280
8376
  // selected index of select elements are cleared on a clone operation.
8281
- if (isIE && dom.select('script,style,select').length > 0) {
8377
+ if (isIE && dom.select('script,style,select,map').length > 0) {
8282
8378
  content = node.innerHTML;
8283
8379
  node = node.cloneNode(false);
8284
8380
  dom.setHTML(node, content);
@@ -9979,6 +10075,11 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9979
10075
  Event.remove(t.id, 'blur', bf);
9980
10076
  });
9981
10077
 
10078
+ //prevent default left and right keys on chrome - so that the keyboard navigation is used.
10079
+ if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
10080
+ return Event.prevent(e);
10081
+ }
10082
+
9982
10083
  if (e.keyCode == 13 || e.keyCode == 32) {
9983
10084
  onChange(e);
9984
10085
  return Event.cancel(e);
@@ -9989,6 +10090,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9989
10090
  }
9990
10091
  });
9991
10092
  })(tinymce);
10093
+
9992
10094
  (function(tinymce) {
9993
10095
  var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9994
10096
 
@@ -10244,6 +10346,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
10244
10346
  }
10245
10347
 
10246
10348
  t.isMenuVisible = 0;
10349
+ t.onHideMenu.dispatch();
10247
10350
  }
10248
10351
  },
10249
10352
 
@@ -11327,14 +11430,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11327
11430
 
11328
11431
  t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
11329
11432
 
11330
- // Firefox 2 doesn't load stylesheets correctly this way
11331
- if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
11332
- for (i = 0; i < t.contentCSS.length; i++)
11333
- t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
11334
-
11335
- t.contentCSS = [];
11336
- }
11337
-
11338
11433
  bi = s.body_id || 'tinymce';
11339
11434
  if (bi.indexOf('=') != -1) {
11340
11435
  bi = t.getParam('body_id', '', 'hash');
@@ -11347,7 +11442,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11347
11442
  bc = bc[t.id] || '';
11348
11443
  }
11349
11444
 
11350
- t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
11445
+ t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"><br></body></html>';
11351
11446
 
11352
11447
  // Domain relaxing enabled, then set document domain
11353
11448
  if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
@@ -11365,7 +11460,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11365
11460
  title : s.aria_label,
11366
11461
  style : {
11367
11462
  width : '100%',
11368
- height : h
11463
+ height : h,
11464
+ display : 'block' // Important for Gecko to render the iframe correctly
11369
11465
  }
11370
11466
  });
11371
11467
 
@@ -11380,50 +11476,27 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11380
11476
  e = n = o = null; // Cleanup
11381
11477
  },
11382
11478
 
11383
- setupIframe : function(filled) {
11479
+ setupIframe : function() {
11384
11480
  var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
11385
11481
 
11386
11482
  // Setup iframe body
11387
- if ((!isIE || !tinymce.relaxedDomain) && !filled) {
11388
- // We need to wait for the load event on Gecko
11389
- if (isGecko && !s.readonly) {
11390
- t.getWin().addEventListener("DOMContentLoaded", function() {
11391
- window.setTimeout(function() {
11392
- var b = t.getBody(), undef;
11393
-
11394
- // Editable element needs to have some contents or backspace/delete won't work properly for some odd reason on FF 3.6 or older
11395
- b.innerHTML = '<br>';
11396
-
11397
- // Check if Gecko supports contentEditable mode FF2 doesn't
11398
- if (b.contentEditable !== undef) {
11399
- // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
11400
- b.contentEditable = false;
11401
- b.contentEditable = true;
11402
-
11403
- // Caret doesn't get rendered when you mousedown on the HTML element on FF 3.x
11404
- t.onMouseDown.add(function(ed, e) {
11405
- if (e.target.nodeName === "HTML") {
11406
- // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
11407
- b.contentEditable = false;
11408
- b.contentEditable = true;
11409
-
11410
- d.designMode = 'on'; // Render the caret
11411
-
11412
- // Remove design mode again after a while so it has some time to execute
11413
- window.setTimeout(function() {
11414
- d.designMode = 'off';
11415
- t.getBody().focus();
11416
- }, 1);
11417
- }
11418
- });
11419
- } else
11420
- d.designMode = 'on';
11421
-
11422
- // Call setup frame once the contentEditable/designMode has been initialized
11423
- // since the caret won't be rendered some times otherwise.
11424
- t.setupIframe(true);
11425
- }, 1);
11426
- }, false);
11483
+ if (!isIE || !tinymce.relaxedDomain) {
11484
+ // Fix for a focus bug in FF 3.x where the body element
11485
+ // wouldn't get proper focus if the user clicked on the HTML element
11486
+ if (isGecko && !Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
11487
+ t.onMouseDown.add(function(ed, e) {
11488
+ if (e.target.nodeName === "HTML") {
11489
+ var body = t.getBody();
11490
+
11491
+ // Blur the body it's focused but not correctly focused
11492
+ body.blur();
11493
+
11494
+ // Refocus the body after a little while
11495
+ setTimeout(function() {
11496
+ body.focus();
11497
+ }, 0);
11498
+ }
11499
+ });
11427
11500
  }
11428
11501
 
11429
11502
  d.open();
@@ -11432,17 +11505,13 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11432
11505
 
11433
11506
  if (tinymce.relaxedDomain)
11434
11507
  d.domain = tinymce.relaxedDomain;
11435
-
11436
- // Wait for iframe onload event on Gecko
11437
- if (isGecko && !s.readonly)
11438
- return;
11439
11508
  }
11440
11509
 
11441
11510
  // It will not steal focus while setting contentEditable
11442
11511
  b = t.getBody();
11443
11512
  b.disabled = true;
11444
11513
 
11445
- if (!isGecko && !s.readonly)
11514
+ if (!s.readonly)
11446
11515
  b.contentEditable = true;
11447
11516
 
11448
11517
  b.disabled = false;
@@ -11592,6 +11661,18 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11592
11661
  subscript : {inline : 'sub'},
11593
11662
  superscript : {inline : 'sup'},
11594
11663
 
11664
+ link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
11665
+ onmatch : function(node) {
11666
+ return true;
11667
+ },
11668
+
11669
+ onformat : function(elm, fmt, vars) {
11670
+ each(vars, function(value, key) {
11671
+ t.dom.setAttrib(elm, key, value);
11672
+ });
11673
+ }
11674
+ },
11675
+
11595
11676
  removeformat : [
11596
11677
  {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
11597
11678
  {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
@@ -11844,6 +11925,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11844
11925
  controlElm = ieRng.item(0);
11845
11926
  }
11846
11927
 
11928
+ t._refreshContentEditable();
11847
11929
  selection.normalize();
11848
11930
 
11849
11931
  // Is not content editable
@@ -12580,16 +12662,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12580
12662
  var t = this, d = t.getDoc(), s = t.settings;
12581
12663
 
12582
12664
  if (isGecko && !s.readonly) {
12583
- if (t._isHidden()) {
12584
- try {
12585
- if (!s.content_editable) {
12586
- d.body.contentEditable = false;
12587
- d.body.contentEditable = true;
12588
- }
12589
- } catch (ex) {
12590
- // Fails if it's hidden
12591
- }
12592
- }
12665
+ t._refreshContentEditable();
12593
12666
 
12594
12667
  try {
12595
12668
  // Try new Gecko method
@@ -12865,7 +12938,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12865
12938
  if (tinymce.isWebKit) {
12866
12939
  dom.bind(t.getDoc(), 'selectionchange', function() {
12867
12940
  if (t.selectionTimer) {
12868
- window.clearTimeout(t.selectionTimer);
12941
+ clearTimeout(t.selectionTimer);
12869
12942
  t.selectionTimer = 0;
12870
12943
  }
12871
12944
 
@@ -12927,6 +13000,21 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12927
13000
  }
12928
13001
  },
12929
13002
 
13003
+ _refreshContentEditable : function() {
13004
+ var self = this, body, parent;
13005
+
13006
+ // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
13007
+ if (self._isHidden()) {
13008
+ body = self.getBody();
13009
+ parent = body.parentNode;
13010
+
13011
+ parent.removeChild(body);
13012
+ parent.appendChild(body);
13013
+
13014
+ body.focus();
13015
+ }
13016
+ },
13017
+
12930
13018
  _isHidden : function() {
12931
13019
  var s;
12932
13020
 
@@ -12949,6 +13037,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12949
13037
  selection = editor.selection,
12950
13038
  commands = {state: {}, exec : {}, value : {}},
12951
13039
  settings = editor.settings,
13040
+ formatter = editor.formatter,
12952
13041
  bookmark;
12953
13042
 
12954
13043
  function execCommand(command, ui, value) {
@@ -13014,11 +13103,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13014
13103
  };
13015
13104
 
13016
13105
  function isFormatMatch(name) {
13017
- return editor.formatter.match(name);
13106
+ return formatter.match(name);
13018
13107
  };
13019
13108
 
13020
13109
  function toggleFormat(name, value) {
13021
- editor.formatter.toggle(name, value ? {value : value} : undefined);
13110
+ formatter.toggle(name, value ? {value : value} : undefined);
13022
13111
  };
13023
13112
 
13024
13113
  function storeSelection(type) {
@@ -13078,7 +13167,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13078
13167
  // Remove all other alignments first
13079
13168
  each('left,center,right,full'.split(','), function(name) {
13080
13169
  if (align != name)
13081
- editor.formatter.remove('align' + name);
13170
+ formatter.remove('align' + name);
13082
13171
  });
13083
13172
 
13084
13173
  toggleFormat('align' + align);
@@ -13135,7 +13224,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13135
13224
  },
13136
13225
 
13137
13226
  RemoveFormat : function(command) {
13138
- editor.formatter.remove(command);
13227
+ formatter.remove(command);
13139
13228
  },
13140
13229
 
13141
13230
  mceBlockQuote : function(command) {
@@ -13358,7 +13447,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13358
13447
  },
13359
13448
 
13360
13449
  mceToggleFormat : function(command, ui, value) {
13361
- editor.formatter.toggle(value);
13450
+ formatter.toggle(value);
13362
13451
  },
13363
13452
 
13364
13453
  InsertHorizontalRule : function() {
@@ -13375,47 +13464,27 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13375
13464
  },
13376
13465
 
13377
13466
  mceInsertLink : function(command, ui, value) {
13378
- var link = dom.getParent(selection.getNode(), 'a'), img, style, cls;
13467
+ var anchor;
13379
13468
 
13380
- if (tinymce.is(value, 'string'))
13469
+ if (typeof(value) == 'string')
13381
13470
  value = {href : value};
13382
13471
 
13472
+ anchor = dom.getParent(selection.getNode(), 'a');
13473
+
13383
13474
  // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
13384
13475
  value.href = value.href.replace(' ', '%20');
13385
13476
 
13386
- if (!link) {
13387
- // WebKit can't create links on floated images for some odd reason
13388
- // So, just remove styles and restore it later
13389
- if (tinymce.isWebKit) {
13390
- img = dom.getParent(selection.getNode(), 'img');
13391
-
13392
- if (img) {
13393
- style = img.style.cssText;
13394
- cls = img.className;
13395
- img.style.cssText = null;
13396
- img.className = null;
13397
- }
13398
- }
13399
-
13400
- execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
13477
+ // Remove existing links if there could be child links or that the href isn't specified
13478
+ if (!anchor || !value.href) {
13479
+ formatter.remove('link');
13480
+ }
13401
13481
 
13402
- // Restore styles
13403
- if (style)
13404
- img.style.cssText = style;
13405
- if (cls)
13406
- img.className = cls;
13407
-
13408
- each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
13409
- dom.setAttribs(link, value);
13410
- });
13411
- } else {
13412
- if (value.href)
13413
- dom.setAttribs(link, value);
13414
- else
13415
- editor.dom.remove(link, TRUE);
13482
+ // Apply new link to selection
13483
+ if (value.href) {
13484
+ formatter.apply('link', value, anchor);
13416
13485
  }
13417
13486
  },
13418
-
13487
+
13419
13488
  selectAll : function() {
13420
13489
  var root = dom.getRoot(), rng = dom.createRng();
13421
13490
 
@@ -13987,6 +14056,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13987
14056
  ra.setStart(en, 0);
13988
14057
  }
13989
14058
 
14059
+ // If the body is totally empty add a BR element this might happen on webkit
14060
+ if (!d.body.hasChildNodes()) {
14061
+ d.body.appendChild(dom.create('br'));
14062
+ }
14063
+
13990
14064
  // Never use body as start or end node
13991
14065
  sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13992
14066
  sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
@@ -14846,6 +14920,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14846
14920
  fmt = fmt || format;
14847
14921
 
14848
14922
  if (elm) {
14923
+ if (fmt.onformat) {
14924
+ fmt.onformat(elm, fmt, vars, node);
14925
+ }
14926
+
14849
14927
  each(fmt.styles, function(value, name) {
14850
14928
  dom.setStyle(elm, name, replaceVars(value, vars));
14851
14929
  });
@@ -14863,7 +14941,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14863
14941
  }
14864
14942
  };
14865
14943
  function adjustSelectionToVisibleSelection() {
14866
-
14867
14944
  function findSelectionEnd(start, end) {
14868
14945
  var walker = new TreeWalker(end);
14869
14946
  for (node = walker.current(); node; node = walker.prev()) {
@@ -14871,37 +14948,49 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14871
14948
  return node;
14872
14949
  }
14873
14950
  }
14874
- }
14951
+ };
14875
14952
 
14876
14953
  // Adjust selection so that a end container with a end offset of zero is not included in the selection
14877
14954
  // as this isn't visible to the user.
14878
14955
  var rng = ed.selection.getRng();
14879
14956
  var start = rng.startContainer;
14880
14957
  var end = rng.endContainer;
14958
+
14881
14959
  if (start != end && rng.endOffset == 0) {
14882
14960
  var newEnd = findSelectionEnd(start, end);
14883
14961
  var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
14962
+
14884
14963
  rng.setEnd(newEnd, endOffset);
14885
14964
  }
14965
+
14886
14966
  return rng;
14887
14967
  }
14888
14968
 
14889
14969
  function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
14890
- var nodes =[], listIndex =-1, list, startIndex = -1, endIndex = -1, currentWrapElm;
14970
+ var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
14891
14971
 
14892
14972
  // find the index of the first child list.
14893
14973
  each(node.childNodes, function(n, index) {
14894
- if (n.nodeName==="UL"||n.nodeName==="OL") {listIndex = index; list=n; return false; }
14974
+ if (n.nodeName === "UL" || n.nodeName === "OL") {
14975
+ listIndex = index;
14976
+ list = n;
14977
+ return false;
14978
+ }
14895
14979
  });
14896
14980
 
14897
14981
  // get the index of the bookmarks
14898
14982
  each(node.childNodes, function(n, index) {
14899
- if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_start") {startIndex=index}
14900
- if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_end") {endIndex=index}
14983
+ if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
14984
+ if (n.id == bookmark.id + "_start") {
14985
+ startIndex = index;
14986
+ } else if (n.id == bookmark.id + "_end") {
14987
+ endIndex = index;
14988
+ }
14989
+ }
14901
14990
  });
14902
14991
 
14903
14992
  // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
14904
- if (listIndex<=0 || (startIndex<listIndex&&endIndex>listIndex)) {
14993
+ if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
14905
14994
  each(tinymce.grep(node.childNodes), process);
14906
14995
  return 0;
14907
14996
  } else {
@@ -14909,22 +14998,26 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14909
14998
 
14910
14999
  // create a list of the nodes on the same side of the list as the selection
14911
15000
  each(tinymce.grep(node.childNodes), function(n, index) {
14912
- if ((startIndex<listIndex && index <listIndex) || (startIndex>listIndex && index >listIndex)) {
15001
+ if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
14913
15002
  nodes.push(n);
14914
- n.parentNode.removeChild(n);
15003
+ n.parentNode.removeChild(n);
14915
15004
  }
14916
15005
  });
14917
15006
 
14918
15007
  // insert the wrapping element either before or after the list.
14919
- if (startIndex<listIndex) {
15008
+ if (startIndex < listIndex) {
14920
15009
  node.insertBefore(currentWrapElm, list);
14921
- } else if (startIndex>listIndex) {
15010
+ } else if (startIndex > listIndex) {
14922
15011
  node.insertBefore(currentWrapElm, list.nextSibling);
14923
15012
  }
14924
15013
 
14925
15014
  // add the new nodes to the list.
14926
15015
  newWrappers.push(currentWrapElm);
14927
- each(nodes, function(node){currentWrapElm.appendChild(node)});
15016
+
15017
+ each(nodes, function(node) {
15018
+ currentWrapElm.appendChild(node);
15019
+ });
15020
+
14928
15021
  return currentWrapElm;
14929
15022
  }
14930
15023
  };
@@ -15138,7 +15231,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
15138
15231
  }
15139
15232
 
15140
15233
  // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
15141
- if (node) {
15234
+ if (node && format.merge_siblings !== false) {
15142
15235
  node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
15143
15236
  node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
15144
15237
  }
@@ -15404,6 +15497,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
15404
15497
  function matchItems(node, format, item_name) {
15405
15498
  var key, value, items = format[item_name], i;
15406
15499
 
15500
+ // Custom match
15501
+ if (format.onmatch) {
15502
+ return format.onmatch(node, format, item_name);
15503
+ }
15504
+
15407
15505
  // Check all items
15408
15506
  if (items) {
15409
15507
  // Non indexed object