tinymce-rails 4.0.26 → 4.0.28

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- // 4.0.26 (2014-05-06)
1
+ // 4.0.28 (2014-05-27)
2
2
 
3
3
  /**
4
4
  * Compiled inline version. (Library mode)
@@ -97,6 +97,11 @@
97
97
  /*jshint loopfunc:true*/
98
98
  /*eslint no-loop-func:0 */
99
99
 
100
+ /**
101
+ * This class wraps the browsers native event logic with more convenient methods.
102
+ *
103
+ * @class tinymce.dom.EventUtils
104
+ */
100
105
  define("tinymce/dom/EventUtils", [], function() {
101
106
  "use strict";
102
107
 
@@ -657,7 +662,7 @@ define("tinymce/dom/EventUtils", [], function() {
657
662
  */
658
663
 
659
664
  /*jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */
660
- /*eslint dot-notation:0, no-empty:0, no-cond-assign:0, no-unused-expressions:0, new-cap:0, no-nested-ternary:0, func-style:0, no-bitwise: 0 */
665
+ /*eslint dot-notation:0, no-empty:0, no-cond-assign:0, no-unused-expressions:0, new-cap:0, no-nested-ternary:0, func-style:0, no-bitwise:0, max-len:0, brace-style:0 */
661
666
 
662
667
  /*
663
668
  * Sizzle CSS Selector Engine
@@ -3344,10 +3349,15 @@ define("tinymce/html/Styles", [], function() {
3344
3349
  urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
3345
3350
  styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
3346
3351
  trimRightRegExp = /\s+$/,
3347
- undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF';
3352
+ undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
3348
3353
 
3349
3354
  settings = settings || {};
3350
3355
 
3356
+ if (schema) {
3357
+ validStyles = schema.getValidStyles();
3358
+ invalidStyles = schema.getInvalidStyles();
3359
+ }
3360
+
3351
3361
  encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
3352
3362
  for (i = 0; i < encodingItems.length; i++) {
3353
3363
  encodingLookup[encodingItems[i]] = invisibleChar + i;
@@ -3605,16 +3615,16 @@ define("tinymce/html/Styles", [], function() {
3605
3615
  *
3606
3616
  * @method serialize
3607
3617
  * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
3608
- * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
3618
+ * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
3609
3619
  * @return {String} String representation of the style object for example: border: 1px solid red.
3610
3620
  */
3611
- serialize: function(styles, element_name) {
3621
+ serialize: function(styles, elementName) {
3612
3622
  var css = '', name, value;
3613
3623
 
3614
3624
  function serializeStyles(name) {
3615
3625
  var styleList, i, l, value;
3616
3626
 
3617
- styleList = schema.styles[name];
3627
+ styleList = validStyles[name];
3618
3628
  if (styleList) {
3619
3629
  for (i = 0, l = styleList.length; i < l; i++) {
3620
3630
  name = styleList[i];
@@ -3627,18 +3637,36 @@ define("tinymce/html/Styles", [], function() {
3627
3637
  }
3628
3638
  }
3629
3639
 
3640
+ function isValid(name, elementName) {
3641
+ var styleMap;
3642
+
3643
+ styleMap = invalidStyles['*'];
3644
+ if (styleMap && styleMap[name]) {
3645
+ return false;
3646
+ }
3647
+
3648
+ styleMap = invalidStyles[elementName];
3649
+ if (styleMap && styleMap[name]) {
3650
+ return false;
3651
+ }
3652
+
3653
+ return true;
3654
+ }
3655
+
3630
3656
  // Serialize styles according to schema
3631
- if (element_name && schema && schema.styles) {
3657
+ if (elementName && validStyles) {
3632
3658
  // Serialize global styles and element specific styles
3633
3659
  serializeStyles('*');
3634
- serializeStyles(element_name);
3660
+ serializeStyles(elementName);
3635
3661
  } else {
3636
3662
  // Output the styles in the order they are inside the object
3637
3663
  for (name in styles) {
3638
3664
  value = styles[name];
3639
3665
 
3640
3666
  if (value !== undef && value.length > 0) {
3641
- css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
3667
+ if (!invalidStyles || isValid(name, elementName)) {
3668
+ css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
3669
+ }
3642
3670
  }
3643
3671
  }
3644
3672
  }
@@ -5912,7 +5940,9 @@ define("tinymce/dom/DOMUtils", [
5912
5940
  selectorVal = selector;
5913
5941
 
5914
5942
  if (selector === '*') {
5915
- selector = function(node) {return node.nodeType == 1;};
5943
+ selector = function(node) {
5944
+ return node.nodeType == 1;
5945
+ };
5916
5946
  } else {
5917
5947
  selector = function(node) {
5918
5948
  return self.is(node, selectorVal);
@@ -6229,7 +6259,7 @@ define("tinymce/dom/DOMUtils", [
6229
6259
  });
6230
6260
 
6231
6261
  // Default px suffix on these
6232
- if (typeof(value) === 'number' && !numericCssMap[name]) {
6262
+ if (((typeof(value) === 'number') || /^[\-0-9\.]+$/.test(value)) && !numericCssMap[name]) {
6233
6263
  value += 'px';
6234
6264
  }
6235
6265
 
@@ -8990,7 +9020,8 @@ define("tinymce/html/Schema", [
8990
9020
  if (type != "html5-strict") {
8991
9021
  addAttrs("script", "language xml:space");
8992
9022
  addAttrs("style", "xml:space");
8993
- addAttrs("object", "declare classid codebase codetype archive standby align border hspace vspace");
9023
+ addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
9024
+ addAttrs("embed", "align name hspace vspace");
8994
9025
  addAttrs("param", "valuetype type");
8995
9026
  addAttrs("a", "charset name rev shape coords");
8996
9027
  addAttrs("br", "clear");
@@ -9059,6 +9090,27 @@ define("tinymce/html/Schema", [
9059
9090
  return schema;
9060
9091
  }
9061
9092
 
9093
+ function compileElementMap(value, mode) {
9094
+ var styles;
9095
+
9096
+ if (value) {
9097
+ styles = {};
9098
+
9099
+ if (typeof value == 'string') {
9100
+ value = {
9101
+ '*': value
9102
+ };
9103
+ }
9104
+
9105
+ // Convert styles into a rule list
9106
+ each(value, function(value, key) {
9107
+ styles[key] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
9108
+ });
9109
+ }
9110
+
9111
+ return styles;
9112
+ }
9113
+
9062
9114
  /**
9063
9115
  * Constructs a new Schema instance.
9064
9116
  *
@@ -9067,9 +9119,10 @@ define("tinymce/html/Schema", [
9067
9119
  * @param {Object} settings Name/value settings object.
9068
9120
  */
9069
9121
  return function(settings) {
9070
- var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
9071
- var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap;
9072
- var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, customElementsMap = {}, specialElements = {};
9122
+ var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
9123
+ var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
9124
+ var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, textInlineElementsMap;
9125
+ var customElementsMap = {}, specialElements = {};
9073
9126
 
9074
9127
  // Creates an lookup table map object for the specified option or the default value
9075
9128
  function createLookupTable(option, default_value, extendWith) {
@@ -9101,15 +9154,9 @@ define("tinymce/html/Schema", [
9101
9154
  settings.valid_elements = '*[*]';
9102
9155
  }
9103
9156
 
9104
- // Build styles list
9105
- if (settings.valid_styles) {
9106
- validStyles = {};
9107
-
9108
- // Convert styles into a rule list
9109
- each(settings.valid_styles, function(value, key) {
9110
- validStyles[key] = explode(value);
9111
- });
9112
- }
9157
+ validStyles = compileElementMap(settings.valid_styles);
9158
+ invalidStyles = compileElementMap(settings.invalid_styles, 'map');
9159
+ validClasses = compileElementMap(settings.valid_classes, 'map');
9113
9160
 
9114
9161
  // Setup map objects
9115
9162
  whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
@@ -9124,6 +9171,8 @@ define("tinymce/html/Schema", [
9124
9171
  blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
9125
9172
  'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
9126
9173
  'datalist select optgroup', textBlockElementsMap);
9174
+ textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
9175
+ 'dfn code mark q sup sub samp');
9127
9176
 
9128
9177
  each((settings.special || 'script noscript style textarea').split(' '), function(name) {
9129
9178
  specialElements[name] = new RegExp('<\/' + name + '[^>]*>','gi');
@@ -9480,10 +9529,32 @@ define("tinymce/html/Schema", [
9480
9529
  /**
9481
9530
  * Name/value map object with valid styles for each element.
9482
9531
  *
9483
- * @field styles
9532
+ * @method getValidStyles
9533
+ * @type Object
9534
+ */
9535
+ self.getValidStyles = function() {
9536
+ return validStyles;
9537
+ };
9538
+
9539
+ /**
9540
+ * Name/value map object with valid styles for each element.
9541
+ *
9542
+ * @method getInvalidStyles
9484
9543
  * @type Object
9485
9544
  */
9486
- self.styles = validStyles;
9545
+ self.getInvalidStyles = function() {
9546
+ return invalidStyles;
9547
+ };
9548
+
9549
+ /**
9550
+ * Name/value map object with valid classes for each element.
9551
+ *
9552
+ * @method getValidClasses
9553
+ * @type Object
9554
+ */
9555
+ self.getValidClasses = function() {
9556
+ return validClasses;
9557
+ };
9487
9558
 
9488
9559
  /**
9489
9560
  * Returns a map with boolean attributes.
@@ -9515,6 +9586,16 @@ define("tinymce/html/Schema", [
9515
9586
  return textBlockElementsMap;
9516
9587
  };
9517
9588
 
9589
+ /**
9590
+ * Returns a map of inline text format nodes for example strong/span or ins.
9591
+ *
9592
+ * @method getTextInlineElements
9593
+ * @return {Object} Name/value lookup map for text format elements.
9594
+ */
9595
+ self.getTextInlineElements = function() {
9596
+ return textInlineElementsMap;
9597
+ };
9598
+
9518
9599
  /**
9519
9600
  * Returns a map with short ended elements such as BR or IMG.
9520
9601
  *
@@ -9744,6 +9825,36 @@ define("tinymce/html/SaxParser", [
9744
9825
  ], function(Schema, Entities, Tools) {
9745
9826
  var each = Tools.each;
9746
9827
 
9828
+ /**
9829
+ * Returns the index of the end tag for a specific start tag. This can be
9830
+ * used to skip all children of a parent element from being processed.
9831
+ */
9832
+ function skipUntilEndTag(schema, html, startIndex) {
9833
+ var count = 1, matches, tokenRegExp, shortEndedElements;
9834
+
9835
+ shortEndedElements = schema.getShortEndedElements();
9836
+ tokenRegExp = /<([!?\/])?([A-Za-z0-9\-\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
9837
+ tokenRegExp.lastIndex = startIndex;
9838
+
9839
+ while ((matches = tokenRegExp.exec(html))) {
9840
+ if (matches[1] === '/') { // End element
9841
+ count--;
9842
+ } else if (!matches[1]) { // Start element
9843
+ if (matches[2] in shortEndedElements) {
9844
+ continue;
9845
+ }
9846
+
9847
+ count++;
9848
+ }
9849
+
9850
+ if (count === 0) {
9851
+ break;
9852
+ }
9853
+ }
9854
+
9855
+ return tokenRegExp.lastIndex;
9856
+ }
9857
+
9747
9858
  /**
9748
9859
  * Constructs a new SaxParser instance.
9749
9860
  *
@@ -10025,7 +10136,13 @@ define("tinymce/html/SaxParser", [
10025
10136
  }
10026
10137
 
10027
10138
  // Invalidate element if it's marked as bogus
10028
- if (attrList.map['data-mce-bogus']) {
10139
+ if ((attr = attrList.map['data-mce-bogus'])) {
10140
+ if (attr === 'all') {
10141
+ index = skipUntilEndTag(schema, html, tokenRegExp.lastIndex);
10142
+ tokenRegExp.lastIndex = index;
10143
+ continue;
10144
+ }
10145
+
10029
10146
  isValidElement = false;
10030
10147
  }
10031
10148
  }
@@ -10688,7 +10805,7 @@ define("tinymce/html/DomParser", [
10688
10805
  // Leave nodes that have a name like <a name="name">
10689
10806
  if (!node.attributes.map.name && !node.attributes.map.id) {
10690
10807
  tempNode = node.parent;
10691
- node.empty().remove();
10808
+ node.unwrap();
10692
10809
  node = tempNode;
10693
10810
  return;
10694
10811
  }
@@ -10867,6 +10984,48 @@ define("tinymce/html/DomParser", [
10867
10984
  }
10868
10985
  });
10869
10986
  }
10987
+
10988
+ if (settings.validate && schema.getValidClasses()) {
10989
+ self.addAttributeFilter('class', function(nodes) {
10990
+ var i = nodes.length, node, classList, ci, className, classValue;
10991
+ var validClasses = schema.getValidClasses(), validClassesMap, valid;
10992
+
10993
+ while (i--) {
10994
+ node = nodes[i];
10995
+ classList = node.attr('class').split(' ');
10996
+ classValue = '';
10997
+
10998
+ for (ci = 0; ci < classList.length; ci++) {
10999
+ className = classList[ci];
11000
+ valid = false;
11001
+
11002
+ validClassesMap = validClasses['*'];
11003
+ if (validClassesMap && validClassesMap[className]) {
11004
+ valid = true;
11005
+ }
11006
+
11007
+ validClassesMap = validClasses[node.name];
11008
+ if (!valid && validClassesMap && !validClassesMap[className]) {
11009
+ valid = true;
11010
+ }
11011
+
11012
+ if (valid) {
11013
+ if (classValue) {
11014
+ classValue += ' ';
11015
+ }
11016
+
11017
+ classValue += className;
11018
+ }
11019
+ }
11020
+
11021
+ if (!classValue.length) {
11022
+ classValue = null;
11023
+ }
11024
+
11025
+ node.attr('class', classValue);
11026
+ }
11027
+ });
11028
+ }
10870
11029
  };
10871
11030
  });
10872
11031
 
@@ -11349,15 +11508,6 @@ define("tinymce/dom/Serializer", [
11349
11508
  }
11350
11509
  });
11351
11510
 
11352
- // Remove expando attributes
11353
- htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) {
11354
- var i = nodes.length;
11355
-
11356
- while (i--) {
11357
- nodes[i].attr(name, null);
11358
- }
11359
- });
11360
-
11361
11511
  htmlParser.addNodeFilter('noscript', function(nodes) {
11362
11512
  var i = nodes.length, node;
11363
11513
 
@@ -11372,7 +11522,7 @@ define("tinymce/dom/Serializer", [
11372
11522
 
11373
11523
  // Force script into CDATA sections and remove the mce- prefix also add comments around styles
11374
11524
  htmlParser.addNodeFilter('script,style', function(nodes, name) {
11375
- var i = nodes.length, node, value;
11525
+ var i = nodes.length, node, value, type;
11376
11526
 
11377
11527
  function trim(value) {
11378
11528
  /*jshint maxlen:255 */
@@ -11388,9 +11538,12 @@ define("tinymce/dom/Serializer", [
11388
11538
  value = node.firstChild ? node.firstChild.value : '';
11389
11539
 
11390
11540
  if (name === "script") {
11391
- // Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5)
11392
- var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, '');
11393
- node.attr('type', type === 'text/javascript' ? null : type);
11541
+ // Remove mce- prefix from script elements and remove default type since the user specified
11542
+ // a script element without type attribute
11543
+ type = node.attr('type');
11544
+ if (type) {
11545
+ node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
11546
+ }
11394
11547
 
11395
11548
  if (value.length > 0) {
11396
11549
  node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
@@ -11457,13 +11610,19 @@ define("tinymce/dom/Serializer", [
11457
11610
  }
11458
11611
 
11459
11612
  // Remove internal data attributes
11460
- htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) {
11461
- var i = nodes.length;
11613
+ htmlParser.addAttributeFilter(
11614
+ 'data-mce-src,data-mce-href,data-mce-style,' +
11615
+ 'data-mce-selected,data-mce-expando,' +
11616
+ 'data-mce-type,data-mce-resize',
11462
11617
 
11463
- while (i--) {
11464
- nodes[i].attr(name, null);
11618
+ function(nodes, name) {
11619
+ var i = nodes.length;
11620
+
11621
+ while (i--) {
11622
+ nodes[i].attr(name, null);
11623
+ }
11465
11624
  }
11466
- });
11625
+ );
11467
11626
 
11468
11627
  // Return public methods
11469
11628
  return {
@@ -11799,15 +11958,17 @@ define("tinymce/dom/TridentSelection", [], function() {
11799
11958
 
11800
11959
  // Find the text node and offset
11801
11960
  while (sibling) {
11802
- nodeValue = sibling.nodeValue;
11803
- textNodeOffset += nodeValue.length;
11804
-
11805
- // We are at or passed the position we where looking for
11806
- if (textNodeOffset >= offset) {
11807
- container = sibling;
11808
- textNodeOffset -= offset;
11809
- textNodeOffset = nodeValue.length - textNodeOffset;
11810
- break;
11961
+ if (sibling.nodeType == 3) {
11962
+ nodeValue = sibling.nodeValue;
11963
+ textNodeOffset += nodeValue.length;
11964
+
11965
+ // We are at or passed the position we where looking for
11966
+ if (textNodeOffset >= offset) {
11967
+ container = sibling;
11968
+ textNodeOffset -= offset;
11969
+ textNodeOffset = nodeValue.length - textNodeOffset;
11970
+ break;
11971
+ }
11811
11972
  }
11812
11973
 
11813
11974
  sibling = sibling.nextSibling;
@@ -11832,13 +11993,15 @@ define("tinymce/dom/TridentSelection", [], function() {
11832
11993
  }
11833
11994
 
11834
11995
  while (sibling) {
11835
- textNodeOffset += sibling.nodeValue.length;
11996
+ if (sibling.nodeType == 3) {
11997
+ textNodeOffset += sibling.nodeValue.length;
11836
11998
 
11837
- // We are at or passed the position we where looking for
11838
- if (textNodeOffset >= offset) {
11839
- container = sibling;
11840
- textNodeOffset -= offset;
11841
- break;
11999
+ // We are at or passed the position we where looking for
12000
+ if (textNodeOffset >= offset) {
12001
+ container = sibling;
12002
+ textNodeOffset -= offset;
12003
+ break;
12004
+ }
11842
12005
  }
11843
12006
 
11844
12007
  sibling = sibling.previousSibling;
@@ -12171,8 +12334,8 @@ define("tinymce/util/VK", [
12171
12334
  },
12172
12335
 
12173
12336
  metaKeyPressed: function(e) {
12174
- // Check if ctrl or meta key is pressed also check if alt is false for Polish users
12175
- return (Env.mac ? e.metaKey : e.ctrlKey) && !e.altKey;
12337
+ // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
12338
+ return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
12176
12339
  }
12177
12340
  };
12178
12341
  });
@@ -12704,7 +12867,7 @@ define("tinymce/dom/ControlSelection", [
12704
12867
  // Included from: js/tinymce/classes/dom/RangeUtils.js
12705
12868
 
12706
12869
  /**
12707
- * Range.js
12870
+ * RangeUtils.js
12708
12871
  *
12709
12872
  * Copyright, Moxiecode Systems AB
12710
12873
  * Released under LGPL License.
@@ -12714,7 +12877,7 @@ define("tinymce/dom/ControlSelection", [
12714
12877
  */
12715
12878
 
12716
12879
  /**
12717
- * RangeUtils
12880
+ * This class contains a few utility methods for ranges.
12718
12881
  *
12719
12882
  * @class tinymce.dom.RangeUtils
12720
12883
  * @private
@@ -12725,6 +12888,20 @@ define("tinymce/dom/RangeUtils", [
12725
12888
  ], function(Tools, TreeWalker) {
12726
12889
  var each = Tools.each;
12727
12890
 
12891
+ function getEndChild(container, index) {
12892
+ var childNodes = container.childNodes;
12893
+
12894
+ index--;
12895
+
12896
+ if (index > childNodes.length - 1) {
12897
+ index = childNodes.length - 1;
12898
+ } else if (index < 0) {
12899
+ index = 0;
12900
+ }
12901
+
12902
+ return childNodes[index] || container;
12903
+ }
12904
+
12728
12905
  function RangeUtils(dom) {
12729
12906
  /**
12730
12907
  * Walks the specified range like object and executes the callback for each sibling collection it finds.
@@ -12837,7 +13014,7 @@ define("tinymce/dom/RangeUtils", [
12837
13014
 
12838
13015
  // If index based end position then resolve it
12839
13016
  if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
12840
- endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
13017
+ endContainer = getEndChild(endContainer, endOffset);
12841
13018
  }
12842
13019
 
12843
13020
  // Same container
@@ -13184,6 +13361,398 @@ define("tinymce/dom/RangeUtils", [
13184
13361
  return RangeUtils;
13185
13362
  });
13186
13363
 
13364
+ // Included from: js/tinymce/classes/dom/BookmarkManager.js
13365
+
13366
+ /**
13367
+ * BookmarkManager.js
13368
+ *
13369
+ * Copyright, Moxiecode Systems AB
13370
+ * Released under LGPL License.
13371
+ *
13372
+ * License: http://www.tinymce.com/license
13373
+ * Contributing: http://www.tinymce.com/contributing
13374
+ */
13375
+
13376
+ /**
13377
+ * This class handles selection bookmarks.
13378
+ *
13379
+ * @class tinymce.dom.BookmarkManager
13380
+ */
13381
+ define("tinymce/dom/BookmarkManager", [
13382
+ "tinymce/Env",
13383
+ "tinymce/util/Tools"
13384
+ ], function(Env, Tools) {
13385
+ /**
13386
+ * Constructs a new BookmarkManager instance for a specific selection instance.
13387
+ *
13388
+ * @constructor
13389
+ * @method BookmarkManager
13390
+ * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
13391
+ */
13392
+ function BookmarkManager(selection) {
13393
+ var dom = selection.dom;
13394
+
13395
+ /**
13396
+ * Returns a bookmark location for the current selection. This bookmark object
13397
+ * can then be used to restore the selection after some content modification to the document.
13398
+ *
13399
+ * @method getBookmark
13400
+ * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
13401
+ * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
13402
+ * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
13403
+ * @example
13404
+ * // Stores a bookmark of the current selection
13405
+ * var bm = tinymce.activeEditor.selection.getBookmark();
13406
+ *
13407
+ * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
13408
+ *
13409
+ * // Restore the selection bookmark
13410
+ * tinymce.activeEditor.selection.moveToBookmark(bm);
13411
+ */
13412
+ this.getBookmark = function(type, normalized) {
13413
+ var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
13414
+
13415
+ function findIndex(name, element) {
13416
+ var index = 0;
13417
+
13418
+ Tools.each(dom.select(name), function(node, i) {
13419
+ if (node == element) {
13420
+ index = i;
13421
+ }
13422
+ });
13423
+
13424
+ return index;
13425
+ }
13426
+
13427
+ function normalizeTableCellSelection(rng) {
13428
+ function moveEndPoint(start) {
13429
+ var container, offset, childNodes, prefix = start ? 'start' : 'end';
13430
+
13431
+ container = rng[prefix + 'Container'];
13432
+ offset = rng[prefix + 'Offset'];
13433
+
13434
+ if (container.nodeType == 1 && container.nodeName == "TR") {
13435
+ childNodes = container.childNodes;
13436
+ container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
13437
+ if (container) {
13438
+ offset = start ? 0 : container.childNodes.length;
13439
+ rng['set' + (start ? 'Start' : 'End')](container, offset);
13440
+ }
13441
+ }
13442
+ }
13443
+
13444
+ moveEndPoint(true);
13445
+ moveEndPoint();
13446
+
13447
+ return rng;
13448
+ }
13449
+
13450
+ function getLocation() {
13451
+ var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
13452
+
13453
+ function getPoint(rng, start) {
13454
+ var container = rng[start ? 'startContainer' : 'endContainer'],
13455
+ offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
13456
+
13457
+ if (container.nodeType == 3) {
13458
+ if (normalized) {
13459
+ for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
13460
+ offset += node.nodeValue.length;
13461
+ }
13462
+ }
13463
+
13464
+ point.push(offset);
13465
+ } else {
13466
+ childNodes = container.childNodes;
13467
+
13468
+ if (offset >= childNodes.length && childNodes.length) {
13469
+ after = 1;
13470
+ offset = Math.max(0, childNodes.length - 1);
13471
+ }
13472
+
13473
+ point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
13474
+ }
13475
+
13476
+ for (; container && container != root; container = container.parentNode) {
13477
+ point.push(dom.nodeIndex(container, normalized));
13478
+ }
13479
+
13480
+ return point;
13481
+ }
13482
+
13483
+ bookmark.start = getPoint(rng, true);
13484
+
13485
+ if (!selection.isCollapsed()) {
13486
+ bookmark.end = getPoint(rng);
13487
+ }
13488
+
13489
+ return bookmark;
13490
+ }
13491
+
13492
+ if (type == 2) {
13493
+ element = selection.getNode();
13494
+ name = element ? element.nodeName : null;
13495
+
13496
+ if (name == 'IMG') {
13497
+ return {name: name, index: findIndex(name, element)};
13498
+ }
13499
+
13500
+ if (selection.tridentSel) {
13501
+ return selection.tridentSel.getBookmark(type);
13502
+ }
13503
+
13504
+ return getLocation();
13505
+ }
13506
+
13507
+ // Handle simple range
13508
+ if (type) {
13509
+ return {rng: selection.getRng()};
13510
+ }
13511
+
13512
+ rng = selection.getRng();
13513
+ id = dom.uniqueId();
13514
+ collapsed = selection.isCollapsed();
13515
+ styles = 'overflow:hidden;line-height:0px';
13516
+
13517
+ // Explorer method
13518
+ if (rng.duplicate || rng.item) {
13519
+ // Text selection
13520
+ if (!rng.item) {
13521
+ rng2 = rng.duplicate();
13522
+
13523
+ try {
13524
+ // Insert start marker
13525
+ rng.collapse();
13526
+ rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
13527
+
13528
+ // Insert end marker
13529
+ if (!collapsed) {
13530
+ rng2.collapse(false);
13531
+
13532
+ // Detect the empty space after block elements in IE and move the
13533
+ // end back one character <p></p>] becomes <p>]</p>
13534
+ rng.moveToElementText(rng2.parentElement());
13535
+ if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
13536
+ rng2.move('character', -1);
13537
+ }
13538
+
13539
+ rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
13540
+ }
13541
+ } catch (ex) {
13542
+ // IE might throw unspecified error so lets ignore it
13543
+ return null;
13544
+ }
13545
+ } else {
13546
+ // Control selection
13547
+ element = rng.item(0);
13548
+ name = element.nodeName;
13549
+
13550
+ return {name: name, index: findIndex(name, element)};
13551
+ }
13552
+ } else {
13553
+ element = selection.getNode();
13554
+ name = element.nodeName;
13555
+ if (name == 'IMG') {
13556
+ return {name: name, index: findIndex(name, element)};
13557
+ }
13558
+
13559
+ // W3C method
13560
+ rng2 = normalizeTableCellSelection(rng.cloneRange());
13561
+
13562
+ // Insert end marker
13563
+ if (!collapsed) {
13564
+ rng2.collapse(false);
13565
+ rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
13566
+ }
13567
+
13568
+ rng = normalizeTableCellSelection(rng);
13569
+ rng.collapse(true);
13570
+ rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
13571
+ }
13572
+
13573
+ selection.moveToBookmark({id: id, keep: 1});
13574
+
13575
+ return {id: id};
13576
+ };
13577
+
13578
+ /**
13579
+ * Restores the selection to the specified bookmark.
13580
+ *
13581
+ * @method moveToBookmark
13582
+ * @param {Object} bookmark Bookmark to restore selection from.
13583
+ * @return {Boolean} true/false if it was successful or not.
13584
+ * @example
13585
+ * // Stores a bookmark of the current selection
13586
+ * var bm = tinymce.activeEditor.selection.getBookmark();
13587
+ *
13588
+ * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
13589
+ *
13590
+ * // Restore the selection bookmark
13591
+ * tinymce.activeEditor.selection.moveToBookmark(bm);
13592
+ */
13593
+ this.moveToBookmark = function(bookmark) {
13594
+ var rng, root, startContainer, endContainer, startOffset, endOffset;
13595
+
13596
+ function setEndPoint(start) {
13597
+ var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
13598
+
13599
+ if (point) {
13600
+ offset = point[0];
13601
+
13602
+ // Find container node
13603
+ for (node = root, i = point.length - 1; i >= 1; i--) {
13604
+ children = node.childNodes;
13605
+
13606
+ if (point[i] > children.length - 1) {
13607
+ return;
13608
+ }
13609
+
13610
+ node = children[point[i]];
13611
+ }
13612
+
13613
+ // Move text offset to best suitable location
13614
+ if (node.nodeType === 3) {
13615
+ offset = Math.min(point[0], node.nodeValue.length);
13616
+ }
13617
+
13618
+ // Move element offset to best suitable location
13619
+ if (node.nodeType === 1) {
13620
+ offset = Math.min(point[0], node.childNodes.length);
13621
+ }
13622
+
13623
+ // Set offset within container node
13624
+ if (start) {
13625
+ rng.setStart(node, offset);
13626
+ } else {
13627
+ rng.setEnd(node, offset);
13628
+ }
13629
+ }
13630
+
13631
+ return true;
13632
+ }
13633
+
13634
+ function restoreEndPoint(suffix) {
13635
+ var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
13636
+
13637
+ if (marker) {
13638
+ node = marker.parentNode;
13639
+
13640
+ if (suffix == 'start') {
13641
+ if (!keep) {
13642
+ idx = dom.nodeIndex(marker);
13643
+ } else {
13644
+ node = marker.firstChild;
13645
+ idx = 1;
13646
+ }
13647
+
13648
+ startContainer = endContainer = node;
13649
+ startOffset = endOffset = idx;
13650
+ } else {
13651
+ if (!keep) {
13652
+ idx = dom.nodeIndex(marker);
13653
+ } else {
13654
+ node = marker.firstChild;
13655
+ idx = 1;
13656
+ }
13657
+
13658
+ endContainer = node;
13659
+ endOffset = idx;
13660
+ }
13661
+
13662
+ if (!keep) {
13663
+ prev = marker.previousSibling;
13664
+ next = marker.nextSibling;
13665
+
13666
+ // Remove all marker text nodes
13667
+ Tools.each(Tools.grep(marker.childNodes), function(node) {
13668
+ if (node.nodeType == 3) {
13669
+ node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
13670
+ }
13671
+ });
13672
+
13673
+ // Remove marker but keep children if for example contents where inserted into the marker
13674
+ // Also remove duplicated instances of the marker for example by a
13675
+ // split operation or by WebKit auto split on paste feature
13676
+ while ((marker = dom.get(bookmark.id + '_' + suffix))) {
13677
+ dom.remove(marker, 1);
13678
+ }
13679
+
13680
+ // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
13681
+ // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
13682
+ // isn't worth the effort. Sorry, Opera but it's just a fact
13683
+ if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
13684
+ idx = prev.nodeValue.length;
13685
+ prev.appendData(next.nodeValue);
13686
+ dom.remove(next);
13687
+
13688
+ if (suffix == 'start') {
13689
+ startContainer = endContainer = prev;
13690
+ startOffset = endOffset = idx;
13691
+ } else {
13692
+ endContainer = prev;
13693
+ endOffset = idx;
13694
+ }
13695
+ }
13696
+ }
13697
+ }
13698
+ }
13699
+
13700
+ function addBogus(node) {
13701
+ // Adds a bogus BR element for empty block elements
13702
+ if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
13703
+ node.innerHTML = '<br data-mce-bogus="1" />';
13704
+ }
13705
+
13706
+ return node;
13707
+ }
13708
+
13709
+ if (bookmark) {
13710
+ if (bookmark.start) {
13711
+ rng = dom.createRng();
13712
+ root = dom.getRoot();
13713
+
13714
+ if (selection.tridentSel) {
13715
+ return selection.tridentSel.moveToBookmark(bookmark);
13716
+ }
13717
+
13718
+ if (setEndPoint(true) && setEndPoint()) {
13719
+ selection.setRng(rng);
13720
+ }
13721
+ } else if (bookmark.id) {
13722
+ // Restore start/end points
13723
+ restoreEndPoint('start');
13724
+ restoreEndPoint('end');
13725
+
13726
+ if (startContainer) {
13727
+ rng = dom.createRng();
13728
+ rng.setStart(addBogus(startContainer), startOffset);
13729
+ rng.setEnd(addBogus(endContainer), endOffset);
13730
+ selection.setRng(rng);
13731
+ }
13732
+ } else if (bookmark.name) {
13733
+ selection.select(dom.select(bookmark.name)[bookmark.index]);
13734
+ } else if (bookmark.rng) {
13735
+ selection.setRng(bookmark.rng);
13736
+ }
13737
+ }
13738
+ };
13739
+ }
13740
+
13741
+ /**
13742
+ * Returns true/false if the specified node is a bookmark node or not.
13743
+ *
13744
+ * @static
13745
+ * @method isBookmarkNode
13746
+ * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
13747
+ * @return {Boolean} true/false if the node is a bookmark node or not.
13748
+ */
13749
+ BookmarkManager.isBookmarkNode = function(node) {
13750
+ return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
13751
+ };
13752
+
13753
+ return BookmarkManager;
13754
+ });
13755
+
13187
13756
  // Included from: js/tinymce/classes/dom/Selection.js
13188
13757
 
13189
13758
  /**
@@ -13210,11 +13779,12 @@ define("tinymce/dom/Selection", [
13210
13779
  "tinymce/dom/TridentSelection",
13211
13780
  "tinymce/dom/ControlSelection",
13212
13781
  "tinymce/dom/RangeUtils",
13782
+ "tinymce/dom/BookmarkManager",
13213
13783
  "tinymce/Env",
13214
13784
  "tinymce/util/Tools"
13215
- ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, Env, Tools) {
13216
- var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
13217
- var isIE = Env.ie, isOpera = Env.opera;
13785
+ ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) {
13786
+ var each = Tools.each, trim = Tools.trim;
13787
+ var isIE = Env.ie;
13218
13788
 
13219
13789
  /**
13220
13790
  * Constructs a new selection instance.
@@ -13232,7 +13802,7 @@ define("tinymce/dom/Selection", [
13232
13802
  self.win = win;
13233
13803
  self.serializer = serializer;
13234
13804
  self.editor = editor;
13235
-
13805
+ self.bookmarkManager = new BookmarkManager(self);
13236
13806
  self.controlSelection = new ControlSelection(self, editor);
13237
13807
 
13238
13808
  // No W3C Range support
@@ -13532,169 +14102,7 @@ define("tinymce/dom/Selection", [
13532
14102
  * tinymce.activeEditor.selection.moveToBookmark(bm);
13533
14103
  */
13534
14104
  getBookmark: function(type, normalized) {
13535
- var self = this, dom = self.dom, rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
13536
-
13537
- function findIndex(name, element) {
13538
- var index = 0;
13539
-
13540
- each(dom.select(name), function(node, i) {
13541
- if (node == element) {
13542
- index = i;
13543
- }
13544
- });
13545
-
13546
- return index;
13547
- }
13548
-
13549
- function normalizeTableCellSelection(rng) {
13550
- function moveEndPoint(start) {
13551
- var container, offset, childNodes, prefix = start ? 'start' : 'end';
13552
-
13553
- container = rng[prefix + 'Container'];
13554
- offset = rng[prefix + 'Offset'];
13555
-
13556
- if (container.nodeType == 1 && container.nodeName == "TR") {
13557
- childNodes = container.childNodes;
13558
- container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
13559
- if (container) {
13560
- offset = start ? 0 : container.childNodes.length;
13561
- rng['set' + (start ? 'Start' : 'End')](container, offset);
13562
- }
13563
- }
13564
- }
13565
-
13566
- moveEndPoint(true);
13567
- moveEndPoint();
13568
-
13569
- return rng;
13570
- }
13571
-
13572
- function getLocation() {
13573
- var rng = self.getRng(true), root = dom.getRoot(), bookmark = {};
13574
-
13575
- function getPoint(rng, start) {
13576
- var container = rng[start ? 'startContainer' : 'endContainer'],
13577
- offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
13578
-
13579
- if (container.nodeType == 3) {
13580
- if (normalized) {
13581
- for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
13582
- offset += node.nodeValue.length;
13583
- }
13584
- }
13585
-
13586
- point.push(offset);
13587
- } else {
13588
- childNodes = container.childNodes;
13589
-
13590
- if (offset >= childNodes.length && childNodes.length) {
13591
- after = 1;
13592
- offset = Math.max(0, childNodes.length - 1);
13593
- }
13594
-
13595
- point.push(self.dom.nodeIndex(childNodes[offset], normalized) + after);
13596
- }
13597
-
13598
- for (; container && container != root; container = container.parentNode) {
13599
- point.push(self.dom.nodeIndex(container, normalized));
13600
- }
13601
-
13602
- return point;
13603
- }
13604
-
13605
- bookmark.start = getPoint(rng, true);
13606
-
13607
- if (!self.isCollapsed()) {
13608
- bookmark.end = getPoint(rng);
13609
- }
13610
-
13611
- return bookmark;
13612
- }
13613
-
13614
- if (type == 2) {
13615
- element = self.getNode();
13616
- name = element ? element.nodeName : null;
13617
-
13618
- if (name == 'IMG') {
13619
- return {name: name, index: findIndex(name, element)};
13620
- }
13621
-
13622
- if (self.tridentSel) {
13623
- return self.tridentSel.getBookmark(type);
13624
- }
13625
-
13626
- return getLocation();
13627
- }
13628
-
13629
- // Handle simple range
13630
- if (type) {
13631
- return {rng: self.getRng()};
13632
- }
13633
-
13634
- rng = self.getRng();
13635
- id = dom.uniqueId();
13636
- collapsed = self.isCollapsed();
13637
- styles = 'overflow:hidden;line-height:0px';
13638
-
13639
- // Explorer method
13640
- if (rng.duplicate || rng.item) {
13641
- // Text selection
13642
- if (!rng.item) {
13643
- rng2 = rng.duplicate();
13644
-
13645
- try {
13646
- // Insert start marker
13647
- rng.collapse();
13648
- rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
13649
-
13650
- // Insert end marker
13651
- if (!collapsed) {
13652
- rng2.collapse(false);
13653
-
13654
- // Detect the empty space after block elements in IE and move the
13655
- // end back one character <p></p>] becomes <p>]</p>
13656
- rng.moveToElementText(rng2.parentElement());
13657
- if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
13658
- rng2.move('character', -1);
13659
- }
13660
-
13661
- rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
13662
- }
13663
- } catch (ex) {
13664
- // IE might throw unspecified error so lets ignore it
13665
- return null;
13666
- }
13667
- } else {
13668
- // Control selection
13669
- element = rng.item(0);
13670
- name = element.nodeName;
13671
-
13672
- return {name: name, index: findIndex(name, element)};
13673
- }
13674
- } else {
13675
- element = self.getNode();
13676
- name = element.nodeName;
13677
- if (name == 'IMG') {
13678
- return {name: name, index: findIndex(name, element)};
13679
- }
13680
-
13681
- // W3C method
13682
- rng2 = normalizeTableCellSelection(rng.cloneRange());
13683
-
13684
- // Insert end marker
13685
- if (!collapsed) {
13686
- rng2.collapse(false);
13687
- rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
13688
- }
13689
-
13690
- rng = normalizeTableCellSelection(rng);
13691
- rng.collapse(true);
13692
- rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
13693
- }
13694
-
13695
- self.moveToBookmark({id: id, keep: 1});
13696
-
13697
- return {id: id};
14105
+ return this.bookmarkManager.getBookmark(type, normalized);
13698
14106
  },
13699
14107
 
13700
14108
  /**
@@ -13713,150 +14121,7 @@ define("tinymce/dom/Selection", [
13713
14121
  * tinymce.activeEditor.selection.moveToBookmark(bm);
13714
14122
  */
13715
14123
  moveToBookmark: function(bookmark) {
13716
- var self = this, dom = self.dom, rng, root, startContainer, endContainer, startOffset, endOffset;
13717
-
13718
- function setEndPoint(start) {
13719
- var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
13720
-
13721
- if (point) {
13722
- offset = point[0];
13723
-
13724
- // Find container node
13725
- for (node = root, i = point.length - 1; i >= 1; i--) {
13726
- children = node.childNodes;
13727
-
13728
- if (point[i] > children.length - 1) {
13729
- return;
13730
- }
13731
-
13732
- node = children[point[i]];
13733
- }
13734
-
13735
- // Move text offset to best suitable location
13736
- if (node.nodeType === 3) {
13737
- offset = Math.min(point[0], node.nodeValue.length);
13738
- }
13739
-
13740
- // Move element offset to best suitable location
13741
- if (node.nodeType === 1) {
13742
- offset = Math.min(point[0], node.childNodes.length);
13743
- }
13744
-
13745
- // Set offset within container node
13746
- if (start) {
13747
- rng.setStart(node, offset);
13748
- } else {
13749
- rng.setEnd(node, offset);
13750
- }
13751
- }
13752
-
13753
- return true;
13754
- }
13755
-
13756
- function restoreEndPoint(suffix) {
13757
- var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
13758
-
13759
- if (marker) {
13760
- node = marker.parentNode;
13761
-
13762
- if (suffix == 'start') {
13763
- if (!keep) {
13764
- idx = dom.nodeIndex(marker);
13765
- } else {
13766
- node = marker.firstChild;
13767
- idx = 1;
13768
- }
13769
-
13770
- startContainer = endContainer = node;
13771
- startOffset = endOffset = idx;
13772
- } else {
13773
- if (!keep) {
13774
- idx = dom.nodeIndex(marker);
13775
- } else {
13776
- node = marker.firstChild;
13777
- idx = 1;
13778
- }
13779
-
13780
- endContainer = node;
13781
- endOffset = idx;
13782
- }
13783
-
13784
- if (!keep) {
13785
- prev = marker.previousSibling;
13786
- next = marker.nextSibling;
13787
-
13788
- // Remove all marker text nodes
13789
- each(grep(marker.childNodes), function(node) {
13790
- if (node.nodeType == 3) {
13791
- node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
13792
- }
13793
- });
13794
-
13795
- // Remove marker but keep children if for example contents where inserted into the marker
13796
- // Also remove duplicated instances of the marker for example by a
13797
- // split operation or by WebKit auto split on paste feature
13798
- while ((marker = dom.get(bookmark.id + '_' + suffix))) {
13799
- dom.remove(marker, 1);
13800
- }
13801
-
13802
- // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
13803
- // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
13804
- // isn't worth the effort. Sorry, Opera but it's just a fact
13805
- if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) {
13806
- idx = prev.nodeValue.length;
13807
- prev.appendData(next.nodeValue);
13808
- dom.remove(next);
13809
-
13810
- if (suffix == 'start') {
13811
- startContainer = endContainer = prev;
13812
- startOffset = endOffset = idx;
13813
- } else {
13814
- endContainer = prev;
13815
- endOffset = idx;
13816
- }
13817
- }
13818
- }
13819
- }
13820
- }
13821
-
13822
- function addBogus(node) {
13823
- // Adds a bogus BR element for empty block elements
13824
- if (dom.isBlock(node) && !node.innerHTML && !isIE) {
13825
- node.innerHTML = '<br data-mce-bogus="1" />';
13826
- }
13827
-
13828
- return node;
13829
- }
13830
-
13831
- if (bookmark) {
13832
- if (bookmark.start) {
13833
- rng = dom.createRng();
13834
- root = dom.getRoot();
13835
-
13836
- if (self.tridentSel) {
13837
- return self.tridentSel.moveToBookmark(bookmark);
13838
- }
13839
-
13840
- if (setEndPoint(true) && setEndPoint()) {
13841
- self.setRng(rng);
13842
- }
13843
- } else if (bookmark.id) {
13844
- // Restore start/end points
13845
- restoreEndPoint('start');
13846
- restoreEndPoint('end');
13847
-
13848
- if (startContainer) {
13849
- rng = dom.createRng();
13850
- rng.setStart(addBogus(startContainer), startOffset);
13851
- rng.setEnd(addBogus(endContainer), endOffset);
13852
- self.setRng(rng);
13853
- }
13854
- } else if (bookmark.name) {
13855
- self.select(dom.select(bookmark.name)[bookmark.index]);
13856
- } else if (bookmark.rng) {
13857
- self.setRng(bookmark.rng);
13858
- }
13859
- }
14124
+ return this.bookmarkManager.moveToBookmark(bookmark);
13860
14125
  },
13861
14126
 
13862
14127
  /**
@@ -14446,6 +14711,126 @@ define("tinymce/dom/Selection", [
14446
14711
  return Selection;
14447
14712
  });
14448
14713
 
14714
+ // Included from: js/tinymce/classes/dom/ElementUtils.js
14715
+
14716
+ /**
14717
+ * ElementUtils.js
14718
+ *
14719
+ * Copyright, Moxiecode Systems AB
14720
+ * Released under LGPL License.
14721
+ *
14722
+ * License: http://www.tinymce.com/license
14723
+ * Contributing: http://www.tinymce.com/contributing
14724
+ */
14725
+
14726
+ /**
14727
+ * Utility class for various element specific functions.
14728
+ *
14729
+ * @private
14730
+ */
14731
+ define("tinymce/dom/ElementUtils", [
14732
+ "tinymce/dom/BookmarkManager",
14733
+ "tinymce/util/Tools"
14734
+ ], function(BookmarkManager, Tools) {
14735
+ var each = Tools.each;
14736
+
14737
+ function ElementUtils(dom) {
14738
+ /**
14739
+ * Compares two nodes and checks if it's attributes and styles matches.
14740
+ * This doesn't compare classes as items since their order is significant.
14741
+ *
14742
+ * @method compare
14743
+ * @param {Node} node1 First node to compare with.
14744
+ * @param {Node} node2 Second node to compare with.
14745
+ * @return {boolean} True/false if the nodes are the same or not.
14746
+ */
14747
+ this.compare = function(node1, node2) {
14748
+ // Not the same name
14749
+ if (node1.nodeName != node2.nodeName) {
14750
+ return false;
14751
+ }
14752
+
14753
+ /**
14754
+ * Returns all the nodes attributes excluding internal ones, styles and classes.
14755
+ *
14756
+ * @private
14757
+ * @param {Node} node Node to get attributes from.
14758
+ * @return {Object} Name/value object with attributes and attribute values.
14759
+ */
14760
+ function getAttribs(node) {
14761
+ var attribs = {};
14762
+
14763
+ each(dom.getAttribs(node), function(attr) {
14764
+ var name = attr.nodeName.toLowerCase();
14765
+
14766
+ // Don't compare internal attributes or style
14767
+ if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
14768
+ attribs[name] = dom.getAttrib(node, name);
14769
+ }
14770
+ });
14771
+
14772
+ return attribs;
14773
+ }
14774
+
14775
+ /**
14776
+ * Compares two objects checks if it's key + value exists in the other one.
14777
+ *
14778
+ * @private
14779
+ * @param {Object} obj1 First object to compare.
14780
+ * @param {Object} obj2 Second object to compare.
14781
+ * @return {boolean} True/false if the objects matches or not.
14782
+ */
14783
+ function compareObjects(obj1, obj2) {
14784
+ var value, name;
14785
+
14786
+ for (name in obj1) {
14787
+ // Obj1 has item obj2 doesn't have
14788
+ if (obj1.hasOwnProperty(name)) {
14789
+ value = obj2[name];
14790
+
14791
+ // Obj2 doesn't have obj1 item
14792
+ if (typeof value == "undefined") {
14793
+ return false;
14794
+ }
14795
+
14796
+ // Obj2 item has a different value
14797
+ if (obj1[name] != value) {
14798
+ return false;
14799
+ }
14800
+
14801
+ // Delete similar value
14802
+ delete obj2[name];
14803
+ }
14804
+ }
14805
+
14806
+ // Check if obj 2 has something obj 1 doesn't have
14807
+ for (name in obj2) {
14808
+ // Obj2 has item obj1 doesn't have
14809
+ if (obj2.hasOwnProperty(name)) {
14810
+ return false;
14811
+ }
14812
+ }
14813
+
14814
+ return true;
14815
+ }
14816
+
14817
+ // Attribs are not the same
14818
+ if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
14819
+ return false;
14820
+ }
14821
+
14822
+ // Styles are not the same
14823
+ if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
14824
+ return false;
14825
+ }
14826
+
14827
+ return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
14828
+ };
14829
+ }
14830
+
14831
+ return ElementUtils;
14832
+ });
14833
+
14449
14834
  // Included from: js/tinymce/classes/fmt/Preview.js
14450
14835
 
14451
14836
  /**
@@ -14629,9 +15014,11 @@ define("tinymce/fmt/Preview", [
14629
15014
  define("tinymce/Formatter", [
14630
15015
  "tinymce/dom/TreeWalker",
14631
15016
  "tinymce/dom/RangeUtils",
15017
+ "tinymce/dom/BookmarkManager",
15018
+ "tinymce/dom/ElementUtils",
14632
15019
  "tinymce/util/Tools",
14633
15020
  "tinymce/fmt/Preview"
14634
- ], function(TreeWalker, RangeUtils, Tools, Preview) {
15021
+ ], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) {
14635
15022
  /**
14636
15023
  * Constructs a new formatter instance.
14637
15024
  *
@@ -14655,7 +15042,8 @@ define("tinymce/Formatter", [
14655
15042
  undef,
14656
15043
  getContentEditable = dom.getContentEditable,
14657
15044
  disableCaretContainer,
14658
- markCaretContainersBogus;
15045
+ markCaretContainersBogus,
15046
+ isBookmarkNode = BookmarkManager.isBookmarkNode;
14659
15047
 
14660
15048
  var each = Tools.each,
14661
15049
  grep = Tools.grep,
@@ -14680,7 +15068,6 @@ define("tinymce/Formatter", [
14680
15068
 
14681
15069
  function defaultFormats() {
14682
15070
  register({
14683
-
14684
15071
  valigntop: [
14685
15072
  {selector: 'td,th', styles: {'verticalAlign': 'top'}}
14686
15073
  ],
@@ -14971,7 +15358,7 @@ define("tinymce/Formatter", [
14971
15358
 
14972
15359
  // get the index of the bookmarks
14973
15360
  each(node.childNodes, function(n, index) {
14974
- if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
15361
+ if (isBookmarkNode(n)) {
14975
15362
  if (n.id == bookmark.id + "_start") {
14976
15363
  startIndex = index;
14977
15364
  } else if (n.id == bookmark.id + "_end") {
@@ -16547,17 +16934,6 @@ define("tinymce/Formatter", [
16547
16934
  }
16548
16935
  }
16549
16936
 
16550
- /**
16551
- * Checks if the specified node is a bookmark node or not.
16552
- *
16553
- * @private
16554
- * @param {Node} node Node to check if it's a bookmark node or not.
16555
- * @return {Boolean} true/false if the node is a bookmark node.
16556
- */
16557
- function isBookmarkNode(node) {
16558
- return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
16559
- }
16560
-
16561
16937
  /**
16562
16938
  * Merges the next/previous sibling element if they match.
16563
16939
  *
@@ -16567,99 +16943,7 @@ define("tinymce/Formatter", [
16567
16943
  * @return {Node} Next node if we didn't merge and prev node if we did.
16568
16944
  */
16569
16945
  function mergeSiblings(prev, next) {
16570
- var sibling, tmpSibling;
16571
-
16572
- /**
16573
- * Compares two nodes and checks if it's attributes and styles matches.
16574
- * This doesn't compare classes as items since their order is significant.
16575
- *
16576
- * @private
16577
- * @param {Node} node1 First node to compare with.
16578
- * @param {Node} node2 Second node to compare with.
16579
- * @return {boolean} True/false if the nodes are the same or not.
16580
- */
16581
- function compareElements(node1, node2) {
16582
- // Not the same name
16583
- if (node1.nodeName != node2.nodeName) {
16584
- return FALSE;
16585
- }
16586
-
16587
- /**
16588
- * Returns all the nodes attributes excluding internal ones, styles and classes.
16589
- *
16590
- * @private
16591
- * @param {Node} node Node to get attributes from.
16592
- * @return {Object} Name/value object with attributes and attribute values.
16593
- */
16594
- function getAttribs(node) {
16595
- var attribs = {};
16596
-
16597
- each(dom.getAttribs(node), function(attr) {
16598
- var name = attr.nodeName.toLowerCase();
16599
-
16600
- // Don't compare internal attributes or style
16601
- if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
16602
- attribs[name] = dom.getAttrib(node, name);
16603
- }
16604
- });
16605
-
16606
- return attribs;
16607
- }
16608
-
16609
- /**
16610
- * Compares two objects checks if it's key + value exists in the other one.
16611
- *
16612
- * @private
16613
- * @param {Object} obj1 First object to compare.
16614
- * @param {Object} obj2 Second object to compare.
16615
- * @return {boolean} True/false if the objects matches or not.
16616
- */
16617
- function compareObjects(obj1, obj2) {
16618
- var value, name;
16619
-
16620
- for (name in obj1) {
16621
- // Obj1 has item obj2 doesn't have
16622
- if (obj1.hasOwnProperty(name)) {
16623
- value = obj2[name];
16624
-
16625
- // Obj2 doesn't have obj1 item
16626
- if (value === undef) {
16627
- return FALSE;
16628
- }
16629
-
16630
- // Obj2 item has a different value
16631
- if (obj1[name] != value) {
16632
- return FALSE;
16633
- }
16634
-
16635
- // Delete similar value
16636
- delete obj2[name];
16637
- }
16638
- }
16639
-
16640
- // Check if obj 2 has something obj 1 doesn't have
16641
- for (name in obj2) {
16642
- // Obj2 has item obj1 doesn't have
16643
- if (obj2.hasOwnProperty(name)) {
16644
- return FALSE;
16645
- }
16646
- }
16647
-
16648
- return TRUE;
16649
- }
16650
-
16651
- // Attribs are not the same
16652
- if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
16653
- return FALSE;
16654
- }
16655
-
16656
- // Styles are not the same
16657
- if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
16658
- return FALSE;
16659
- }
16660
-
16661
- return !isBookmarkNode(node1) && !isBookmarkNode(node2);
16662
- }
16946
+ var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
16663
16947
 
16664
16948
  function findElementSibling(node, sibling_name) {
16665
16949
  for (sibling = node; sibling; sibling = sibling[sibling_name]) {
@@ -16682,7 +16966,7 @@ define("tinymce/Formatter", [
16682
16966
  next = findElementSibling(next, 'nextSibling');
16683
16967
 
16684
16968
  // Compare next and previous nodes
16685
- if (compareElements(prev, next)) {
16969
+ if (elementUtils.compare(prev, next)) {
16686
16970
  // Append nodes between
16687
16971
  for (sibling = prev.nextSibling; sibling && sibling != next;) {
16688
16972
  tmpSibling = sibling;
@@ -16885,7 +17169,7 @@ define("tinymce/Formatter", [
16885
17169
  node = container;
16886
17170
 
16887
17171
  if (container.nodeType == 3) {
16888
- if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17172
+ if (offset != container.nodeValue.length) {
16889
17173
  hasContentAfter = true;
16890
17174
  }
16891
17175
 
@@ -17467,6 +17751,10 @@ define("tinymce/EnterKey", [
17467
17751
  function trimInlineElementsOnLeftSideOfBlock(block) {
17468
17752
  var node = block, firstChilds = [], i;
17469
17753
 
17754
+ if (!node) {
17755
+ return;
17756
+ }
17757
+
17470
17758
  // Find inner most first child ex: <p><i><b>*</b></i></p>
17471
17759
  while ((node = node.firstChild)) {
17472
17760
  if (dom.isBlock(node)) {
@@ -17507,6 +17795,10 @@ define("tinymce/EnterKey", [
17507
17795
  }
17508
17796
  }
17509
17797
 
17798
+ if (!root) {
17799
+ return;
17800
+ }
17801
+
17510
17802
  // Old IE versions doesn't properly render blocks with br elements in them
17511
17803
  // For example <p><br></p> wont be rendered correctly in a contentEditable area
17512
17804
  // until you remove the br producing <p></p>
@@ -17516,16 +17808,23 @@ define("tinymce/EnterKey", [
17516
17808
  }
17517
17809
  }
17518
17810
 
17519
- if (root.nodeName == 'LI') {
17811
+ if (/^(LI|DT|DD)$/.test(root.nodeName)) {
17520
17812
  var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
17521
17813
 
17522
- if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) {
17814
+ if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
17523
17815
  root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
17524
17816
  }
17525
17817
  }
17526
17818
 
17527
17819
  rng = dom.createRng();
17528
17820
 
17821
+ // Normalize whitespace to remove empty text nodes. Fix for: #6904
17822
+ // Gecko will be able to place the caret in empty text nodes but it won't render propery
17823
+ // Older IE versions will sometimes crash so for now ignore all IE versions
17824
+ if (!Env.ie) {
17825
+ root.normalize();
17826
+ }
17827
+
17529
17828
  if (root.hasChildNodes()) {
17530
17829
  walker = new TreeWalker(root, root);
17531
17830
 
@@ -17589,7 +17888,7 @@ define("tinymce/EnterKey", [
17589
17888
  // Creates a new block element by cloning the current one or creating a new one if the name is specified
17590
17889
  // This function will also copy any text formatting from the parent block and add it to the new one
17591
17890
  function createNewBlock(name) {
17592
- var node = container, block, clonedNode, caretNode;
17891
+ var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
17593
17892
 
17594
17893
  if (name || parentBlockName == "TABLE") {
17595
17894
  block = dom.create(name || newBlockName);
@@ -17603,7 +17902,7 @@ define("tinymce/EnterKey", [
17603
17902
  // Clone any parent styles
17604
17903
  if (settings.keep_styles !== false) {
17605
17904
  do {
17606
- if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U|VAR|CITE|DFN|CODE|MARK|Q|SUP|SUB|SAMP)$/.test(node.nodeName)) {
17905
+ if (textInlineElements[node.nodeName]) {
17607
17906
  // Never clone a caret containers
17608
17907
  if (node.id == '_mce_caret') {
17609
17908
  continue;
@@ -17764,7 +18063,7 @@ define("tinymce/EnterKey", [
17764
18063
  function getContainerBlock() {
17765
18064
  var containerBlockParent = containerBlock.parentNode;
17766
18065
 
17767
- if (containerBlockParent.nodeName == 'LI') {
18066
+ if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
17768
18067
  return containerBlockParent;
17769
18068
  }
17770
18069
 
@@ -17994,8 +18293,8 @@ define("tinymce/EnterKey", [
17994
18293
  parentBlockName = containerBlockName;
17995
18294
  }
17996
18295
 
17997
- // Handle enter in LI
17998
- if (parentBlockName == 'LI') {
18296
+ // Handle enter in list item
18297
+ if (/^(LI|DT|DD)$/.test(parentBlockName)) {
17999
18298
  if (!newBlockName && shiftKey) {
18000
18299
  insertBr();
18001
18300
  return;
@@ -18240,8 +18539,9 @@ define("tinymce/ForceBlocks", [], function() {
18240
18539
  define("tinymce/EditorCommands", [
18241
18540
  "tinymce/html/Serializer",
18242
18541
  "tinymce/Env",
18243
- "tinymce/util/Tools"
18244
- ], function(Serializer, Env, Tools) {
18542
+ "tinymce/util/Tools",
18543
+ "tinymce/dom/ElementUtils"
18544
+ ], function(Serializer, Env, Tools, ElementUtils) {
18245
18545
  // Added for compression purposes
18246
18546
  var each = Tools.each, extend = Tools.extend;
18247
18547
  var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
@@ -18536,7 +18836,8 @@ define("tinymce/EditorCommands", [
18536
18836
 
18537
18837
  mceInsertContent: function(command, ui, value) {
18538
18838
  var parser, serializer, parentNode, rootNode, fragment, args;
18539
- var marker, rng, node, node2, bookmarkHtml;
18839
+ var marker, rng, node, node2, bookmarkHtml, merge;
18840
+ var textInlineElements = editor.schema.getTextInlineElements();
18540
18841
 
18541
18842
  function trimOrPaddLeftRight(html) {
18542
18843
  var rng, container, offset;
@@ -18566,6 +18867,37 @@ define("tinymce/EditorCommands", [
18566
18867
  return html;
18567
18868
  }
18568
18869
 
18870
+ function markInlineFormatElements(fragment) {
18871
+ if (merge) {
18872
+ for (node = fragment.firstChild; node; node = node.walk(true)) {
18873
+ if (textInlineElements[node.name]) {
18874
+ node.attr('data-mce-new', "true");
18875
+ }
18876
+ }
18877
+ }
18878
+ }
18879
+
18880
+ function reduceInlineTextElements() {
18881
+ if (merge) {
18882
+ var root = editor.getBody(), elementUtils = new ElementUtils(dom);
18883
+
18884
+ each(dom.select('*[data-mce-new]'), function(node) {
18885
+ node.removeAttribute('data-mce-new');
18886
+
18887
+ for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
18888
+ if (elementUtils.compare(testNode, node)) {
18889
+ dom.remove(node, true);
18890
+ }
18891
+ }
18892
+ });
18893
+ }
18894
+ }
18895
+
18896
+ if (typeof(value) != 'string') {
18897
+ merge = value.merge;
18898
+ value = value.content;
18899
+ }
18900
+
18569
18901
  // Check for whitespace before/after value
18570
18902
  if (/^ | $/.test(value)) {
18571
18903
  value = trimOrPaddLeftRight(value);
@@ -18613,6 +18945,8 @@ define("tinymce/EditorCommands", [
18613
18945
  var parserArgs = {context: parentNode.nodeName.toLowerCase()};
18614
18946
  fragment = parser.parse(value, parserArgs);
18615
18947
 
18948
+ markInlineFormatElements(fragment);
18949
+
18616
18950
  // Move the caret to a more suitable location
18617
18951
  node = fragment.lastChild;
18618
18952
  if (node.attr('id') == 'mce_marker') {
@@ -18679,6 +19013,8 @@ define("tinymce/EditorCommands", [
18679
19013
  }
18680
19014
  }
18681
19015
 
19016
+ reduceInlineTextElements();
19017
+
18682
19018
  marker = dom.get('mce_marker');
18683
19019
  selection.scrollIntoView(marker);
18684
19020
 
@@ -18981,11 +19317,9 @@ define("tinymce/util/URI", [
18981
19317
  function URI(url, settings) {
18982
19318
  var self = this, baseUri, base_url;
18983
19319
 
18984
- // Trim whitespace
18985
19320
  url = trim(url);
18986
-
18987
- // Default settings
18988
19321
  settings = self.settings = settings || {};
19322
+ baseUri = settings.base_uri;
18989
19323
 
18990
19324
  // Strange app protocol that isn't http/https or local anchor
18991
19325
  // For example: mailto,skype,tel etc.
@@ -18998,7 +19332,7 @@ define("tinymce/util/URI", [
18998
19332
 
18999
19333
  // Absolute path with no host, fake host and protocol
19000
19334
  if (url.indexOf('/') === 0 && !isProtocolRelative) {
19001
- url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url;
19335
+ url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
19002
19336
  }
19003
19337
 
19004
19338
  // Relative path http:// or protocol relative //path
@@ -19007,7 +19341,8 @@ define("tinymce/util/URI", [
19007
19341
  if (settings.base_uri.protocol === "") {
19008
19342
  url = '//mce_host' + self.toAbsPath(base_url, url);
19009
19343
  } else {
19010
- url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url);
19344
+ url = /([^#?]*)([#?]?.*)/.exec(url);
19345
+ url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
19011
19346
  }
19012
19347
  }
19013
19348
 
@@ -19029,7 +19364,6 @@ define("tinymce/util/URI", [
19029
19364
  self[v] = part;
19030
19365
  });
19031
19366
 
19032
- baseUri = settings.base_uri;
19033
19367
  if (baseUri) {
19034
19368
  if (!self.protocol) {
19035
19369
  self.protocol = baseUri.protocol;
@@ -19539,7 +19873,8 @@ define("tinymce/util/EventDispatcher", [
19539
19873
  var nativeEvents = Tools.makeMap(
19540
19874
  "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
19541
19875
  "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
19542
- "draggesture dragdrop drop drag submit",
19876
+ "draggesture dragdrop drop drag submit " +
19877
+ "compositionstart compositionend compositionupdate",
19543
19878
  ' '
19544
19879
  );
19545
19880
 
@@ -20596,9 +20931,11 @@ define("tinymce/ui/DomUtils", [
20596
20931
  ], function(Tools, DOMUtils) {
20597
20932
  "use strict";
20598
20933
 
20934
+ var count = 0;
20935
+
20599
20936
  return {
20600
20937
  id: function() {
20601
- return DOMUtils.DOM.uniqueId();
20938
+ return 'mceu_' + (count++);
20602
20939
  },
20603
20940
 
20604
20941
  createFragment: function(html) {
@@ -23832,9 +24169,117 @@ define("tinymce/ui/FloatPanel", [
23832
24169
  ], function(Panel, Movable, Resizable, DomUtils) {
23833
24170
  "use strict";
23834
24171
 
23835
- var documentClickHandler, documentScrollHandler, visiblePanels = [];
24172
+ var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
23836
24173
  var zOrder = [], hasModal;
23837
24174
 
24175
+ function bindDocumentClickHandler() {
24176
+ function isChildOf(ctrl, parent) {
24177
+ while (ctrl) {
24178
+ if (ctrl == parent) {
24179
+ return true;
24180
+ }
24181
+
24182
+ ctrl = ctrl.parent();
24183
+ }
24184
+ }
24185
+
24186
+ if (!documentClickHandler) {
24187
+ documentClickHandler = function(e) {
24188
+ // Gecko fires click event and in the wrong order on Mac so lets normalize
24189
+ if (e.button == 2) {
24190
+ return;
24191
+ }
24192
+
24193
+ // Hide any float panel when a click is out side that float panel and the
24194
+ // float panels direct parent for example a click on a menu button
24195
+ var i = visiblePanels.length;
24196
+ while (i--) {
24197
+ var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
24198
+
24199
+ if (panel.settings.autohide) {
24200
+ if (clickCtrl) {
24201
+ if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
24202
+ continue;
24203
+ }
24204
+ }
24205
+
24206
+ e = panel.fire('autohide', {target: e.target});
24207
+ if (!e.isDefaultPrevented()) {
24208
+ panel.hide();
24209
+ }
24210
+ }
24211
+ }
24212
+ };
24213
+
24214
+ DomUtils.on(document, 'click', documentClickHandler);
24215
+ }
24216
+ }
24217
+
24218
+ function bindDocumentScrollHandler() {
24219
+ if (!documentScrollHandler) {
24220
+ documentScrollHandler = function() {
24221
+ var i;
24222
+
24223
+ i = visiblePanels.length;
24224
+ while (i--) {
24225
+ repositionPanel(visiblePanels[i]);
24226
+ }
24227
+ };
24228
+
24229
+ DomUtils.on(window, 'scroll', documentScrollHandler);
24230
+ }
24231
+ }
24232
+
24233
+ function bindWindowResizeHandler() {
24234
+ if (!windowResizeHandler) {
24235
+ windowResizeHandler = function() {
24236
+ FloatPanel.hideAll();
24237
+ };
24238
+
24239
+ DomUtils.on(window, 'resize', windowResizeHandler);
24240
+ }
24241
+ }
24242
+
24243
+ /**
24244
+ * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
24245
+ * also reposition all child panels of the current panel.
24246
+ */
24247
+ function repositionPanel(panel) {
24248
+ var scrollY = DomUtils.getViewPort().y;
24249
+
24250
+ function toggleFixedChildPanels(fixed, deltaY) {
24251
+ var parent;
24252
+
24253
+ for (var i = 0; i < visiblePanels.length; i++) {
24254
+ if (visiblePanels[i] != panel) {
24255
+ parent = visiblePanels[i].parent();
24256
+
24257
+ while (parent && (parent = parent.parent())) {
24258
+ if (parent == panel) {
24259
+ visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
24260
+ }
24261
+ }
24262
+ }
24263
+ }
24264
+ }
24265
+
24266
+ if (panel.settings.autofix) {
24267
+ if (!panel._fixed) {
24268
+ panel._autoFixY = panel.layoutRect().y;
24269
+
24270
+ if (panel._autoFixY < scrollY) {
24271
+ panel.fixed(true).layoutRect({y: 0}).repaint();
24272
+ toggleFixedChildPanels(true, scrollY - panel._autoFixY);
24273
+ }
24274
+ } else {
24275
+ if (panel._autoFixY > scrollY) {
24276
+ panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
24277
+ toggleFixedChildPanels(false, panel._autoFixY - scrollY);
24278
+ }
24279
+ }
24280
+ }
24281
+ }
24282
+
23838
24283
  var FloatPanel = Panel.extend({
23839
24284
  Mixins: [Movable, Resizable],
23840
24285
 
@@ -23876,56 +24321,6 @@ define("tinymce/ui/FloatPanel", [
23876
24321
  FloatPanel.currentZIndex = zIndex;
23877
24322
  }
23878
24323
 
23879
- function isChildOf(ctrl, parent) {
23880
- while (ctrl) {
23881
- if (ctrl == parent) {
23882
- return true;
23883
- }
23884
-
23885
- ctrl = ctrl.parent();
23886
- }
23887
- }
23888
-
23889
- /**
23890
- * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
23891
- * also reposition all child panels of the current panel.
23892
- */
23893
- function repositionPanel(panel) {
23894
- var scrollY = DomUtils.getViewPort().y;
23895
-
23896
- function toggleFixedChildPanels(fixed, deltaY) {
23897
- var parent;
23898
-
23899
- for (var i = 0; i < visiblePanels.length; i++) {
23900
- if (visiblePanels[i] != panel) {
23901
- parent = visiblePanels[i].parent();
23902
-
23903
- while (parent && (parent = parent.parent())) {
23904
- if (parent == panel) {
23905
- visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
23906
- }
23907
- }
23908
- }
23909
- }
23910
- }
23911
-
23912
- if (panel.settings.autofix) {
23913
- if (!panel._fixed) {
23914
- panel._autoFixY = panel.layoutRect().y;
23915
-
23916
- if (panel._autoFixY < scrollY) {
23917
- panel.fixed(true).layoutRect({y: 0}).repaint();
23918
- toggleFixedChildPanels(true, scrollY - panel._autoFixY);
23919
- }
23920
- } else {
23921
- if (panel._autoFixY > scrollY) {
23922
- panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
23923
- toggleFixedChildPanels(false, panel._autoFixY - scrollY);
23924
- }
23925
- }
23926
- }
23927
- }
23928
-
23929
24324
  self._super(settings);
23930
24325
  self._eventsRoot = self;
23931
24326
 
@@ -23933,48 +24328,13 @@ define("tinymce/ui/FloatPanel", [
23933
24328
 
23934
24329
  // Hide floatpanes on click out side the root button
23935
24330
  if (settings.autohide) {
23936
- if (!documentClickHandler) {
23937
- documentClickHandler = function(e) {
23938
- // Hide any float panel when a click is out side that float panel and the
23939
- // float panels direct parent for example a click on a menu button
23940
- var i = visiblePanels.length;
23941
- while (i--) {
23942
- var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
23943
-
23944
- if (panel.settings.autohide) {
23945
- if (clickCtrl) {
23946
- if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
23947
- continue;
23948
- }
23949
- }
23950
-
23951
- e = panel.fire('autohide', {target: e.target});
23952
- if (!e.isDefaultPrevented()) {
23953
- panel.hide();
23954
- }
23955
- }
23956
- }
23957
- };
23958
-
23959
- DomUtils.on(document, 'click', documentClickHandler);
23960
- }
23961
-
24331
+ bindDocumentClickHandler();
24332
+ bindWindowResizeHandler();
23962
24333
  visiblePanels.push(self);
23963
24334
  }
23964
24335
 
23965
24336
  if (settings.autofix) {
23966
- if (!documentScrollHandler) {
23967
- documentScrollHandler = function() {
23968
- var i;
23969
-
23970
- i = visiblePanels.length;
23971
- while (i--) {
23972
- repositionPanel(visiblePanels[i]);
23973
- }
23974
- };
23975
-
23976
- DomUtils.on(window, 'scroll', documentScrollHandler);
23977
- }
24337
+ bindDocumentScrollHandler();
23978
24338
 
23979
24339
  self.on('move', function() {
23980
24340
  repositionPanel(this);
@@ -24090,7 +24450,8 @@ define("tinymce/ui/FloatPanel", [
24090
24450
  },
24091
24451
 
24092
24452
  /**
24093
- * Hides all visible the float panels.
24453
+ * Hide all visible float panels with he autohide setting enabled. This is for
24454
+ * manually hiding floating menus or panels.
24094
24455
  *
24095
24456
  * @method hideAll
24096
24457
  */
@@ -24133,7 +24494,8 @@ define("tinymce/ui/FloatPanel", [
24133
24494
  });
24134
24495
 
24135
24496
  /**
24136
- * Hides all visible the float panels.
24497
+ * Hide all visible float panels with he autohide setting enabled. This is for
24498
+ * manually hiding floating menus or panels.
24137
24499
  *
24138
24500
  * @static
24139
24501
  * @method hideAll
@@ -26539,8 +26901,13 @@ define("tinymce/Shortcuts", [
26539
26901
  break;
26540
26902
 
26541
26903
  default:
26542
- shortcut.charCode = value.charCodeAt(0);
26543
- shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
26904
+ // Allow numeric keycodes like ctrl+219 for ctrl+[
26905
+ if (/^[0-9]{2,}$/.test(value)) {
26906
+ shortcut.keyCode = parseInt(value, 10);
26907
+ } else {
26908
+ shortcut.charCode = value.charCodeAt(0);
26909
+ shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
26910
+ }
26544
26911
  }
26545
26912
  });
26546
26913
 
@@ -27347,7 +27714,14 @@ define("tinymce/Editor", [
27347
27714
  // Add internal attribute if we need to we don't on a refresh of the document
27348
27715
  if (!node.attributes.map[internalName]) {
27349
27716
  if (name === "style") {
27350
- node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
27717
+ value = dom.serializeStyle(dom.parseStyle(value), node.name);
27718
+
27719
+ if (!value.length) {
27720
+ value = null;
27721
+ }
27722
+
27723
+ node.attr(internalName, value);
27724
+ node.attr(name, value);
27351
27725
  } else if (name === "tabindex") {
27352
27726
  node.attr(internalName, value);
27353
27727
  node.attr(name, null);
@@ -27364,7 +27738,7 @@ define("tinymce/Editor", [
27364
27738
 
27365
27739
  while (i--) {
27366
27740
  node = nodes[i];
27367
- node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
27741
+ node.attr('type', 'mce-' + (node.attr('type') || 'no/type'));
27368
27742
  }
27369
27743
  });
27370
27744
 
@@ -27978,8 +28352,18 @@ define("tinymce/Editor", [
27978
28352
  }
27979
28353
 
27980
28354
  // Browser commands
27981
- self.getDoc().execCommand(cmd, ui, value);
27982
- self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
28355
+ try {
28356
+ state = self.getDoc().execCommand(cmd, ui, value);
28357
+ } catch (ex) {
28358
+ // Ignore old IE errors
28359
+ }
28360
+
28361
+ if (state) {
28362
+ self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
28363
+ return true;
28364
+ }
28365
+
28366
+ return false;
27983
28367
  },
27984
28368
 
27985
28369
  /**
@@ -28001,8 +28385,8 @@ define("tinymce/Editor", [
28001
28385
  if ((queryItem = self.queryStateCommands[cmd])) {
28002
28386
  returnVal = queryItem.func.call(queryItem.scope);
28003
28387
 
28004
- // Fall though on true
28005
- if (returnVal !== true) {
28388
+ // Fall though on non boolean returns
28389
+ if (returnVal === true || returnVal === false) {
28006
28390
  return returnVal;
28007
28391
  }
28008
28392
  }
@@ -28092,8 +28476,6 @@ define("tinymce/Editor", [
28092
28476
  var self = this, doc = self.getDoc();
28093
28477
 
28094
28478
  if (!self.hidden) {
28095
- self.hidden = true;
28096
-
28097
28479
  // Fixed bug where IE has a blinking cursor left from the editor
28098
28480
  if (ie && doc && !self.inline) {
28099
28481
  doc.execCommand('SelectAll');
@@ -28114,6 +28496,7 @@ define("tinymce/Editor", [
28114
28496
  DOM.setStyle(self.id, 'display', self.orgDisplay);
28115
28497
  }
28116
28498
 
28499
+ self.hidden = true;
28117
28500
  self.fire('hide');
28118
28501
  }
28119
28502
  },
@@ -28377,8 +28760,13 @@ define("tinymce/Editor", [
28377
28760
  *
28378
28761
  * @method insertContent
28379
28762
  * @param {String} content Content to insert.
28763
+ * @param {Object} args Optional args to pass to insert call.
28380
28764
  */
28381
- insertContent: function(content) {
28765
+ insertContent: function(content, args) {
28766
+ if (args) {
28767
+ content = extend({content: content}, args);
28768
+ }
28769
+
28382
28770
  this.execCommand('mceInsertContent', false, content);
28383
28771
  },
28384
28772
 
@@ -29133,7 +29521,7 @@ define("tinymce/EditorManager", [
29133
29521
  * @property minorVersion
29134
29522
  * @type String
29135
29523
  */
29136
- minorVersion : '0.26',
29524
+ minorVersion : '0.28',
29137
29525
 
29138
29526
  /**
29139
29527
  * Release date of TinyMCE build.
@@ -29141,7 +29529,7 @@ define("tinymce/EditorManager", [
29141
29529
  * @property releaseDate
29142
29530
  * @type String
29143
29531
  */
29144
- releaseDate: '2014-05-06',
29532
+ releaseDate: '2014-05-27',
29145
29533
 
29146
29534
  /**
29147
29535
  * Collection of editor instances.
@@ -29177,9 +29565,16 @@ define("tinymce/EditorManager", [
29177
29565
  var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
29178
29566
 
29179
29567
  // Get base URL for the current document
29180
- documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
29181
- if (!/[\/\\]$/.test(documentBaseURL)) {
29182
- documentBaseURL += '/';
29568
+ documentBaseURL = document.location.href;
29569
+
29570
+ // Check if the URL is a document based format like: http://site/dir/file
29571
+ // leave other formats like applewebdata://... intact
29572
+ if (/^[^:]+:\/\/[^\/]+\//.test(documentBaseURL)) {
29573
+ documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
29574
+
29575
+ if (!/[\/\\]$/.test(documentBaseURL)) {
29576
+ documentBaseURL += '/';
29577
+ }
29183
29578
  }
29184
29579
 
29185
29580
  // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
@@ -32113,13 +32508,19 @@ define("tinymce/ui/Form", [
32113
32508
  * @private
32114
32509
  */
32115
32510
  recalcLabels: function() {
32116
- var self = this, maxLabelWidth = 0, labels = [], i, labelGap;
32511
+ var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items;
32117
32512
 
32118
32513
  if (self.settings.labelGapCalc === false) {
32119
32514
  return;
32120
32515
  }
32121
32516
 
32122
- self.items().filter('formitem').each(function(item) {
32517
+ if (self.settings.labelGapCalc == "children") {
32518
+ items = self.find('formitem');
32519
+ } else {
32520
+ items = self.items();
32521
+ }
32522
+
32523
+ items.filter('formitem').each(function(item) {
32123
32524
  var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
32124
32525
 
32125
32526
  maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
@@ -32260,8 +32661,9 @@ define("tinymce/ui/FieldSet", [
32260
32661
  * @extends tinymce.ui.ComboBox
32261
32662
  */
32262
32663
  define("tinymce/ui/FilePicker", [
32263
- "tinymce/ui/ComboBox"
32264
- ], function(ComboBox) {
32664
+ "tinymce/ui/ComboBox",
32665
+ "tinymce/util/Tools"
32666
+ ], function(ComboBox, Tools) {
32265
32667
  "use strict";
32266
32668
 
32267
32669
  return ComboBox.extend({
@@ -32272,12 +32674,17 @@ define("tinymce/ui/FilePicker", [
32272
32674
  * @param {Object} settings Name/value object with settings.
32273
32675
  */
32274
32676
  init: function(settings) {
32275
- var self = this, editor = tinymce.activeEditor, fileBrowserCallback;
32677
+ var self = this, editor = tinymce.activeEditor, fileBrowserCallback, fileBrowserCallbackTypes;
32276
32678
 
32277
32679
  settings.spellcheck = false;
32278
32680
 
32681
+ fileBrowserCallbackTypes = editor.settings.file_browser_callback_types;
32682
+ if (fileBrowserCallbackTypes) {
32683
+ fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
32684
+ }
32685
+
32279
32686
  fileBrowserCallback = editor.settings.file_browser_callback;
32280
- if (fileBrowserCallback) {
32687
+ if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
32281
32688
  settings.icon = 'browse';
32282
32689
 
32283
32690
  settings.onaction = function() {
@@ -32793,6 +33200,7 @@ define("tinymce/ui/FormatControls", [
32793
33200
  }
32794
33201
 
32795
33202
  menuItem.format = formatName;
33203
+ menuItem.cmd = format.cmd;
32796
33204
  }
32797
33205
 
32798
33206
  menu.push(menuItem);
@@ -32839,20 +33247,32 @@ define("tinymce/ui/FormatControls", [
32839
33247
  },
32840
33248
 
32841
33249
  onPostRender: function() {
32842
- var self = this, formatName = this.settings.format;
33250
+ var self = this;
33251
+
33252
+ self.parent().on('show', function() {
33253
+ var formatName, command;
32843
33254
 
32844
- if (formatName) {
32845
- self.parent().on('show', function() {
33255
+ formatName = self.settings.format;
33256
+ if (formatName) {
32846
33257
  self.disabled(!editor.formatter.canApply(formatName));
32847
33258
  self.active(editor.formatter.match(formatName));
32848
- });
32849
- }
33259
+ }
33260
+
33261
+ command = self.settings.cmd;
33262
+ if (command) {
33263
+ self.active(editor.queryCommandState(command));
33264
+ }
33265
+ });
32850
33266
  },
32851
33267
 
32852
33268
  onclick: function() {
32853
33269
  if (this.settings.format) {
32854
33270
  toggleFormat(this.settings.format);
32855
33271
  }
33272
+
33273
+ if (this.settings.cmd) {
33274
+ editor.execCommand(this.settings.cmd);
33275
+ }
32856
33276
  }
32857
33277
  }
32858
33278
  };
@@ -32947,30 +33367,21 @@ define("tinymce/ui/FormatControls", [
32947
33367
  });
32948
33368
  });
32949
33369
 
32950
- function hasUndo() {
32951
- return editor.undoManager ? editor.undoManager.hasUndo() : false;
32952
- }
32953
-
32954
- function hasRedo() {
32955
- return editor.undoManager ? editor.undoManager.hasRedo() : false;
32956
- }
32957
-
32958
- function toggleUndoState() {
32959
- var self = this;
33370
+ function toggleUndoRedoState(type) {
33371
+ return function() {
33372
+ var self = this;
32960
33373
 
32961
- self.disabled(!hasUndo());
32962
- editor.on('Undo Redo AddUndo TypingUndo', function() {
32963
- self.disabled(!hasUndo());
32964
- });
32965
- }
33374
+ type = type == 'redo' ? 'hasRedo' : 'hasUndo';
32966
33375
 
32967
- function toggleRedoState() {
32968
- var self = this;
33376
+ function checkState() {
33377
+ return editor.undoManager ? editor.undoManager[type]() : false;
33378
+ }
32969
33379
 
32970
- self.disabled(!hasRedo());
32971
- editor.on('Undo Redo AddUndo TypingUndo', function() {
32972
- self.disabled(!hasRedo());
32973
- });
33380
+ self.disabled(!checkState());
33381
+ editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() {
33382
+ self.disabled(!checkState());
33383
+ });
33384
+ };
32974
33385
  }
32975
33386
 
32976
33387
  function toggleVisualAidState() {
@@ -32985,13 +33396,13 @@ define("tinymce/ui/FormatControls", [
32985
33396
 
32986
33397
  editor.addButton('undo', {
32987
33398
  tooltip: 'Undo',
32988
- onPostRender: toggleUndoState,
33399
+ onPostRender: toggleUndoRedoState('undo'),
32989
33400
  cmd: 'undo'
32990
33401
  });
32991
33402
 
32992
33403
  editor.addButton('redo', {
32993
33404
  tooltip: 'Redo',
32994
- onPostRender: toggleRedoState,
33405
+ onPostRender: toggleUndoRedoState('redo'),
32995
33406
  cmd: 'redo'
32996
33407
  });
32997
33408
 
@@ -33006,7 +33417,7 @@ define("tinymce/ui/FormatControls", [
33006
33417
  text: 'Undo',
33007
33418
  icon: 'undo',
33008
33419
  shortcut: 'Ctrl+Z',
33009
- onPostRender: toggleUndoState,
33420
+ onPostRender: toggleUndoRedoState('undo'),
33010
33421
  cmd: 'undo'
33011
33422
  });
33012
33423
 
@@ -33014,7 +33425,7 @@ define("tinymce/ui/FormatControls", [
33014
33425
  text: 'Redo',
33015
33426
  icon: 'redo',
33016
33427
  shortcut: 'Ctrl+Y',
33017
- onPostRender: toggleRedoState,
33428
+ onPostRender: toggleUndoRedoState('redo'),
33018
33429
  cmd: 'redo'
33019
33430
  });
33020
33431
 
@@ -33149,7 +33560,14 @@ define("tinymce/ui/FormatControls", [
33149
33560
  var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
33150
33561
 
33151
33562
  each(fontsize_formats.split(' '), function(item) {
33152
- items.push({text: item, value: item});
33563
+ var text = item, value = item;
33564
+ // Allow text=value font sizes.
33565
+ var values = item.split('=');
33566
+ if (values.length > 1) {
33567
+ text = values[0];
33568
+ value = values[1];
33569
+ }
33570
+ items.push({text: text, value: value});
33153
33571
  });
33154
33572
 
33155
33573
  return {
@@ -33991,19 +34409,29 @@ define("tinymce/ui/ListBox", [
33991
34409
  * @setting {Array} values Array with values to add to list box.
33992
34410
  */
33993
34411
  init: function(settings) {
33994
- var self = this, values, i, selected, selectedText, lastItemCtrl;
34412
+ var self = this, values, selected, selectedText, lastItemCtrl;
33995
34413
 
33996
- self._values = values = settings.values;
33997
- if (values) {
33998
- for (i = 0; i < values.length; i++) {
33999
- selected = values[i].selected || settings.value === values[i].value;
34414
+ function setSelected(menuValues) {
34415
+ // Try to find a selected value
34416
+ for (var i = 0; i < menuValues.length; i++) {
34417
+ selected = menuValues[i].selected || settings.value === menuValues[i].value;
34000
34418
 
34001
34419
  if (selected) {
34002
- selectedText = selectedText || values[i].text;
34003
- self._value = values[i].value;
34420
+ selectedText = selectedText || menuValues[i].text;
34421
+ self._value = menuValues[i].value;
34004
34422
  break;
34005
34423
  }
34424
+
34425
+ // If the value has a submenu, try to find the selected values in that menu
34426
+ if (menuValues[i].menu) {
34427
+ setSelected(menuValues[i].menu);
34428
+ }
34006
34429
  }
34430
+ }
34431
+
34432
+ self._values = values = settings.values;
34433
+ if (values) {
34434
+ setSelected(values);
34007
34435
 
34008
34436
  // Default with first item
34009
34437
  if (!selected && values.length > 0) {
@@ -34044,7 +34472,7 @@ define("tinymce/ui/ListBox", [
34044
34472
  * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
34045
34473
  */
34046
34474
  value: function(value) {
34047
- var self = this, active, selectedText, menu, i;
34475
+ var self = this, active, selectedText, menu;
34048
34476
 
34049
34477
  function activateByValue(menu, value) {
34050
34478
  menu.items().each(function(ctrl) {
@@ -34062,20 +34490,28 @@ define("tinymce/ui/ListBox", [
34062
34490
  });
34063
34491
  }
34064
34492
 
34493
+ function setActiveValues(menuValues) {
34494
+ for (var i = 0; i < menuValues.length; i++) {
34495
+ active = menuValues[i].value == value;
34496
+
34497
+ if (active) {
34498
+ selectedText = selectedText || menuValues[i].text;
34499
+ }
34500
+
34501
+ menuValues[i].active = active;
34502
+
34503
+ if (menuValues[i].menu) {
34504
+ setActiveValues(menuValues[i].menu);
34505
+ }
34506
+ }
34507
+ }
34508
+
34065
34509
  if (typeof(value) != "undefined") {
34066
34510
  if (self.menu) {
34067
34511
  activateByValue(self.menu, value);
34068
34512
  } else {
34069
34513
  menu = self.settings.menu;
34070
- for (i = 0; i < menu.length; i++) {
34071
- active = menu[i].value == value;
34072
-
34073
- if (active) {
34074
- selectedText = selectedText || menu[i].text;
34075
- }
34076
-
34077
- menu[i].active = active;
34078
- }
34514
+ setActiveValues(menu);
34079
34515
  }
34080
34516
 
34081
34517
  self.text(selectedText || this.settings.text);
@@ -34217,12 +34653,16 @@ define("tinymce/ui/MenuItem", [
34217
34653
 
34218
34654
  menu = self.menu = Factory.create(menu).parent(self).renderTo();
34219
34655
  menu.reflow();
34220
- menu.fire('show');
34221
34656
  menu.on('cancel', function(e) {
34222
34657
  e.stopPropagation();
34223
34658
  self.focus();
34224
34659
  menu.hide();
34225
34660
  });
34661
+ menu.on('show hide', function(e) {
34662
+ e.control.items().each(function(ctrl) {
34663
+ ctrl.active(ctrl.settings.selected);
34664
+ });
34665
+ }).fire('show');
34226
34666
 
34227
34667
  menu.on('hide', function(e) {
34228
34668
  if (e.control === menu) {
@@ -35319,5 +35759,5 @@ define("tinymce/ui/Throbber", [
35319
35759
  };
35320
35760
  });
35321
35761
 
35322
- expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/RangeUtils","tinymce/dom/Selection","tinymce/fmt/Preview","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
35762
+ expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/dom/ElementUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
35323
35763
  })(this);