tinymce-rails 4.0.26 → 4.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 98010f91779772edeb4ccd7b425414a2715f5d21
4
- data.tar.gz: cf3ffd85b9a52c9ec7a6bae0cc07cafe5b9de8de
3
+ metadata.gz: 3d61de30fa04cae313a1896b6cb9326acb089427
4
+ data.tar.gz: c505df76105015340cc701707fda4801201756ee
5
5
  SHA512:
6
- metadata.gz: 46b1d81eda6b817413d6620ec69693c45c080f44eeb8be4e629cfd7ef05fbd6810e19e343c5a778ed5b5189f4addf28246bc308d87c9d3cddd8ec5f001be5144
7
- data.tar.gz: c4fce036672158aa0a41f7c664030e011e6a6bf7bf300141a1fdf7a0c0133c29c829b570eca9ea154b4d0efb7000244fa29eb4be48b552d246b85c2c8b86892d
6
+ metadata.gz: 43bd86134162aa09da12d021d390f0795a1eeaa064b4eaa7b9a3daa5ce07cca60a777ac350c9cde96d495acd094a4a116f7020067c1b71a4cf2aec8513659399
7
+ data.tar.gz: cdf88d954d3b887f44a2a4f4e9834c5657a87659e9e9440a44d748dc52150957bc5d63dcb4a5cac3fe2d5543c4e930890d2870917e4e30435e46209d7c08ee0e
data/README.md CHANGED
@@ -25,8 +25,9 @@ Be sure to add to the global group, not the `assets` group. Then run `bundle ins
25
25
  **2. Create a `config/tinymce.yml` file with your global configuration options:**
26
26
 
27
27
  ```yml
28
- toolbar1: styleselect | bold italic | link image | undo redo
29
- toolbar2: table | fullscreen
28
+ toolbar:
29
+ - styleselect | bold italic | link image | undo redo
30
+ - table | fullscreen
30
31
  plugins:
31
32
  - table
32
33
  - fullscreen
@@ -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)
@@ -146,10 +146,15 @@ define("tinymce/html/Styles", [], function() {
146
146
  urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
147
147
  styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
148
148
  trimRightRegExp = /\s+$/,
149
- undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF';
149
+ undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
150
150
 
151
151
  settings = settings || {};
152
152
 
153
+ if (schema) {
154
+ validStyles = schema.getValidStyles();
155
+ invalidStyles = schema.getInvalidStyles();
156
+ }
157
+
153
158
  encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
154
159
  for (i = 0; i < encodingItems.length; i++) {
155
160
  encodingLookup[encodingItems[i]] = invisibleChar + i;
@@ -407,16 +412,16 @@ define("tinymce/html/Styles", [], function() {
407
412
  *
408
413
  * @method serialize
409
414
  * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
410
- * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
415
+ * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
411
416
  * @return {String} String representation of the style object for example: border: 1px solid red.
412
417
  */
413
- serialize: function(styles, element_name) {
418
+ serialize: function(styles, elementName) {
414
419
  var css = '', name, value;
415
420
 
416
421
  function serializeStyles(name) {
417
422
  var styleList, i, l, value;
418
423
 
419
- styleList = schema.styles[name];
424
+ styleList = validStyles[name];
420
425
  if (styleList) {
421
426
  for (i = 0, l = styleList.length; i < l; i++) {
422
427
  name = styleList[i];
@@ -429,18 +434,36 @@ define("tinymce/html/Styles", [], function() {
429
434
  }
430
435
  }
431
436
 
437
+ function isValid(name, elementName) {
438
+ var styleMap;
439
+
440
+ styleMap = invalidStyles['*'];
441
+ if (styleMap && styleMap[name]) {
442
+ return false;
443
+ }
444
+
445
+ styleMap = invalidStyles[elementName];
446
+ if (styleMap && styleMap[name]) {
447
+ return false;
448
+ }
449
+
450
+ return true;
451
+ }
452
+
432
453
  // Serialize styles according to schema
433
- if (element_name && schema && schema.styles) {
454
+ if (elementName && validStyles) {
434
455
  // Serialize global styles and element specific styles
435
456
  serializeStyles('*');
436
- serializeStyles(element_name);
457
+ serializeStyles(elementName);
437
458
  } else {
438
459
  // Output the styles in the order they are inside the object
439
460
  for (name in styles) {
440
461
  value = styles[name];
441
462
 
442
463
  if (value !== undef && value.length > 0) {
443
- css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
464
+ if (!invalidStyles || isValid(name, elementName)) {
465
+ css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
466
+ }
444
467
  }
445
468
  }
446
469
  }
@@ -466,6 +489,11 @@ define("tinymce/html/Styles", [], function() {
466
489
  /*jshint loopfunc:true*/
467
490
  /*eslint no-loop-func:0 */
468
491
 
492
+ /**
493
+ * This class wraps the browsers native event logic with more convenient methods.
494
+ *
495
+ * @class tinymce.dom.EventUtils
496
+ */
469
497
  define("tinymce/dom/EventUtils", [], function() {
470
498
  "use strict";
471
499
 
@@ -3274,7 +3302,9 @@ define("tinymce/dom/DOMUtils", [
3274
3302
  selectorVal = selector;
3275
3303
 
3276
3304
  if (selector === '*') {
3277
- selector = function(node) {return node.nodeType == 1;};
3305
+ selector = function(node) {
3306
+ return node.nodeType == 1;
3307
+ };
3278
3308
  } else {
3279
3309
  selector = function(node) {
3280
3310
  return self.is(node, selectorVal);
@@ -3591,7 +3621,7 @@ define("tinymce/dom/DOMUtils", [
3591
3621
  });
3592
3622
 
3593
3623
  // Default px suffix on these
3594
- if (typeof(value) === 'number' && !numericCssMap[name]) {
3624
+ if (((typeof(value) === 'number') || /^[\-0-9\.]+$/.test(value)) && !numericCssMap[name]) {
3595
3625
  value += 'px';
3596
3626
  }
3597
3627
 
@@ -6352,7 +6382,8 @@ define("tinymce/html/Schema", [
6352
6382
  if (type != "html5-strict") {
6353
6383
  addAttrs("script", "language xml:space");
6354
6384
  addAttrs("style", "xml:space");
6355
- addAttrs("object", "declare classid codebase codetype archive standby align border hspace vspace");
6385
+ addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
6386
+ addAttrs("embed", "align name hspace vspace");
6356
6387
  addAttrs("param", "valuetype type");
6357
6388
  addAttrs("a", "charset name rev shape coords");
6358
6389
  addAttrs("br", "clear");
@@ -6421,6 +6452,27 @@ define("tinymce/html/Schema", [
6421
6452
  return schema;
6422
6453
  }
6423
6454
 
6455
+ function compileElementMap(value, mode) {
6456
+ var styles;
6457
+
6458
+ if (value) {
6459
+ styles = {};
6460
+
6461
+ if (typeof value == 'string') {
6462
+ value = {
6463
+ '*': value
6464
+ };
6465
+ }
6466
+
6467
+ // Convert styles into a rule list
6468
+ each(value, function(value, key) {
6469
+ styles[key] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
6470
+ });
6471
+ }
6472
+
6473
+ return styles;
6474
+ }
6475
+
6424
6476
  /**
6425
6477
  * Constructs a new Schema instance.
6426
6478
  *
@@ -6429,9 +6481,10 @@ define("tinymce/html/Schema", [
6429
6481
  * @param {Object} settings Name/value settings object.
6430
6482
  */
6431
6483
  return function(settings) {
6432
- var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
6433
- var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap;
6434
- var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, customElementsMap = {}, specialElements = {};
6484
+ var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
6485
+ var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
6486
+ var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, textInlineElementsMap;
6487
+ var customElementsMap = {}, specialElements = {};
6435
6488
 
6436
6489
  // Creates an lookup table map object for the specified option or the default value
6437
6490
  function createLookupTable(option, default_value, extendWith) {
@@ -6463,15 +6516,9 @@ define("tinymce/html/Schema", [
6463
6516
  settings.valid_elements = '*[*]';
6464
6517
  }
6465
6518
 
6466
- // Build styles list
6467
- if (settings.valid_styles) {
6468
- validStyles = {};
6469
-
6470
- // Convert styles into a rule list
6471
- each(settings.valid_styles, function(value, key) {
6472
- validStyles[key] = explode(value);
6473
- });
6474
- }
6519
+ validStyles = compileElementMap(settings.valid_styles);
6520
+ invalidStyles = compileElementMap(settings.invalid_styles, 'map');
6521
+ validClasses = compileElementMap(settings.valid_classes, 'map');
6475
6522
 
6476
6523
  // Setup map objects
6477
6524
  whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
@@ -6486,6 +6533,8 @@ define("tinymce/html/Schema", [
6486
6533
  blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
6487
6534
  'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
6488
6535
  'datalist select optgroup', textBlockElementsMap);
6536
+ textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
6537
+ 'dfn code mark q sup sub samp');
6489
6538
 
6490
6539
  each((settings.special || 'script noscript style textarea').split(' '), function(name) {
6491
6540
  specialElements[name] = new RegExp('<\/' + name + '[^>]*>','gi');
@@ -6842,10 +6891,32 @@ define("tinymce/html/Schema", [
6842
6891
  /**
6843
6892
  * Name/value map object with valid styles for each element.
6844
6893
  *
6845
- * @field styles
6894
+ * @method getValidStyles
6895
+ * @type Object
6896
+ */
6897
+ self.getValidStyles = function() {
6898
+ return validStyles;
6899
+ };
6900
+
6901
+ /**
6902
+ * Name/value map object with valid styles for each element.
6903
+ *
6904
+ * @method getInvalidStyles
6905
+ * @type Object
6906
+ */
6907
+ self.getInvalidStyles = function() {
6908
+ return invalidStyles;
6909
+ };
6910
+
6911
+ /**
6912
+ * Name/value map object with valid classes for each element.
6913
+ *
6914
+ * @method getValidClasses
6846
6915
  * @type Object
6847
6916
  */
6848
- self.styles = validStyles;
6917
+ self.getValidClasses = function() {
6918
+ return validClasses;
6919
+ };
6849
6920
 
6850
6921
  /**
6851
6922
  * Returns a map with boolean attributes.
@@ -6877,6 +6948,16 @@ define("tinymce/html/Schema", [
6877
6948
  return textBlockElementsMap;
6878
6949
  };
6879
6950
 
6951
+ /**
6952
+ * Returns a map of inline text format nodes for example strong/span or ins.
6953
+ *
6954
+ * @method getTextInlineElements
6955
+ * @return {Object} Name/value lookup map for text format elements.
6956
+ */
6957
+ self.getTextInlineElements = function() {
6958
+ return textInlineElementsMap;
6959
+ };
6960
+
6880
6961
  /**
6881
6962
  * Returns a map with short ended elements such as BR or IMG.
6882
6963
  *
@@ -7106,6 +7187,36 @@ define("tinymce/html/SaxParser", [
7106
7187
  ], function(Schema, Entities, Tools) {
7107
7188
  var each = Tools.each;
7108
7189
 
7190
+ /**
7191
+ * Returns the index of the end tag for a specific start tag. This can be
7192
+ * used to skip all children of a parent element from being processed.
7193
+ */
7194
+ function skipUntilEndTag(schema, html, startIndex) {
7195
+ var count = 1, matches, tokenRegExp, shortEndedElements;
7196
+
7197
+ shortEndedElements = schema.getShortEndedElements();
7198
+ tokenRegExp = /<([!?\/])?([A-Za-z0-9\-\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
7199
+ tokenRegExp.lastIndex = startIndex;
7200
+
7201
+ while ((matches = tokenRegExp.exec(html))) {
7202
+ if (matches[1] === '/') { // End element
7203
+ count--;
7204
+ } else if (!matches[1]) { // Start element
7205
+ if (matches[2] in shortEndedElements) {
7206
+ continue;
7207
+ }
7208
+
7209
+ count++;
7210
+ }
7211
+
7212
+ if (count === 0) {
7213
+ break;
7214
+ }
7215
+ }
7216
+
7217
+ return tokenRegExp.lastIndex;
7218
+ }
7219
+
7109
7220
  /**
7110
7221
  * Constructs a new SaxParser instance.
7111
7222
  *
@@ -7387,7 +7498,13 @@ define("tinymce/html/SaxParser", [
7387
7498
  }
7388
7499
 
7389
7500
  // Invalidate element if it's marked as bogus
7390
- if (attrList.map['data-mce-bogus']) {
7501
+ if ((attr = attrList.map['data-mce-bogus'])) {
7502
+ if (attr === 'all') {
7503
+ index = skipUntilEndTag(schema, html, tokenRegExp.lastIndex);
7504
+ tokenRegExp.lastIndex = index;
7505
+ continue;
7506
+ }
7507
+
7391
7508
  isValidElement = false;
7392
7509
  }
7393
7510
  }
@@ -8050,7 +8167,7 @@ define("tinymce/html/DomParser", [
8050
8167
  // Leave nodes that have a name like <a name="name">
8051
8168
  if (!node.attributes.map.name && !node.attributes.map.id) {
8052
8169
  tempNode = node.parent;
8053
- node.empty().remove();
8170
+ node.unwrap();
8054
8171
  node = tempNode;
8055
8172
  return;
8056
8173
  }
@@ -8229,6 +8346,48 @@ define("tinymce/html/DomParser", [
8229
8346
  }
8230
8347
  });
8231
8348
  }
8349
+
8350
+ if (settings.validate && schema.getValidClasses()) {
8351
+ self.addAttributeFilter('class', function(nodes) {
8352
+ var i = nodes.length, node, classList, ci, className, classValue;
8353
+ var validClasses = schema.getValidClasses(), validClassesMap, valid;
8354
+
8355
+ while (i--) {
8356
+ node = nodes[i];
8357
+ classList = node.attr('class').split(' ');
8358
+ classValue = '';
8359
+
8360
+ for (ci = 0; ci < classList.length; ci++) {
8361
+ className = classList[ci];
8362
+ valid = false;
8363
+
8364
+ validClassesMap = validClasses['*'];
8365
+ if (validClassesMap && validClassesMap[className]) {
8366
+ valid = true;
8367
+ }
8368
+
8369
+ validClassesMap = validClasses[node.name];
8370
+ if (!valid && validClassesMap && !validClassesMap[className]) {
8371
+ valid = true;
8372
+ }
8373
+
8374
+ if (valid) {
8375
+ if (classValue) {
8376
+ classValue += ' ';
8377
+ }
8378
+
8379
+ classValue += className;
8380
+ }
8381
+ }
8382
+
8383
+ if (!classValue.length) {
8384
+ classValue = null;
8385
+ }
8386
+
8387
+ node.attr('class', classValue);
8388
+ }
8389
+ });
8390
+ }
8232
8391
  };
8233
8392
  });
8234
8393
 
@@ -8711,15 +8870,6 @@ define("tinymce/dom/Serializer", [
8711
8870
  }
8712
8871
  });
8713
8872
 
8714
- // Remove expando attributes
8715
- htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) {
8716
- var i = nodes.length;
8717
-
8718
- while (i--) {
8719
- nodes[i].attr(name, null);
8720
- }
8721
- });
8722
-
8723
8873
  htmlParser.addNodeFilter('noscript', function(nodes) {
8724
8874
  var i = nodes.length, node;
8725
8875
 
@@ -8734,7 +8884,7 @@ define("tinymce/dom/Serializer", [
8734
8884
 
8735
8885
  // Force script into CDATA sections and remove the mce- prefix also add comments around styles
8736
8886
  htmlParser.addNodeFilter('script,style', function(nodes, name) {
8737
- var i = nodes.length, node, value;
8887
+ var i = nodes.length, node, value, type;
8738
8888
 
8739
8889
  function trim(value) {
8740
8890
  /*jshint maxlen:255 */
@@ -8750,9 +8900,12 @@ define("tinymce/dom/Serializer", [
8750
8900
  value = node.firstChild ? node.firstChild.value : '';
8751
8901
 
8752
8902
  if (name === "script") {
8753
- // Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5)
8754
- var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, '');
8755
- node.attr('type', type === 'text/javascript' ? null : type);
8903
+ // Remove mce- prefix from script elements and remove default type since the user specified
8904
+ // a script element without type attribute
8905
+ type = node.attr('type');
8906
+ if (type) {
8907
+ node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
8908
+ }
8756
8909
 
8757
8910
  if (value.length > 0) {
8758
8911
  node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
@@ -8819,13 +8972,19 @@ define("tinymce/dom/Serializer", [
8819
8972
  }
8820
8973
 
8821
8974
  // Remove internal data attributes
8822
- htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) {
8823
- var i = nodes.length;
8975
+ htmlParser.addAttributeFilter(
8976
+ 'data-mce-src,data-mce-href,data-mce-style,' +
8977
+ 'data-mce-selected,data-mce-expando,' +
8978
+ 'data-mce-type,data-mce-resize',
8824
8979
 
8825
- while (i--) {
8826
- nodes[i].attr(name, null);
8980
+ function(nodes, name) {
8981
+ var i = nodes.length;
8982
+
8983
+ while (i--) {
8984
+ nodes[i].attr(name, null);
8985
+ }
8827
8986
  }
8828
- });
8987
+ );
8829
8988
 
8830
8989
  // Return public methods
8831
8990
  return {
@@ -9161,15 +9320,17 @@ define("tinymce/dom/TridentSelection", [], function() {
9161
9320
 
9162
9321
  // Find the text node and offset
9163
9322
  while (sibling) {
9164
- nodeValue = sibling.nodeValue;
9165
- textNodeOffset += nodeValue.length;
9166
-
9167
- // We are at or passed the position we where looking for
9168
- if (textNodeOffset >= offset) {
9169
- container = sibling;
9170
- textNodeOffset -= offset;
9171
- textNodeOffset = nodeValue.length - textNodeOffset;
9172
- break;
9323
+ if (sibling.nodeType == 3) {
9324
+ nodeValue = sibling.nodeValue;
9325
+ textNodeOffset += nodeValue.length;
9326
+
9327
+ // We are at or passed the position we where looking for
9328
+ if (textNodeOffset >= offset) {
9329
+ container = sibling;
9330
+ textNodeOffset -= offset;
9331
+ textNodeOffset = nodeValue.length - textNodeOffset;
9332
+ break;
9333
+ }
9173
9334
  }
9174
9335
 
9175
9336
  sibling = sibling.nextSibling;
@@ -9194,13 +9355,15 @@ define("tinymce/dom/TridentSelection", [], function() {
9194
9355
  }
9195
9356
 
9196
9357
  while (sibling) {
9197
- textNodeOffset += sibling.nodeValue.length;
9358
+ if (sibling.nodeType == 3) {
9359
+ textNodeOffset += sibling.nodeValue.length;
9198
9360
 
9199
- // We are at or passed the position we where looking for
9200
- if (textNodeOffset >= offset) {
9201
- container = sibling;
9202
- textNodeOffset -= offset;
9203
- break;
9361
+ // We are at or passed the position we where looking for
9362
+ if (textNodeOffset >= offset) {
9363
+ container = sibling;
9364
+ textNodeOffset -= offset;
9365
+ break;
9366
+ }
9204
9367
  }
9205
9368
 
9206
9369
  sibling = sibling.previousSibling;
@@ -9533,8 +9696,8 @@ define("tinymce/util/VK", [
9533
9696
  },
9534
9697
 
9535
9698
  metaKeyPressed: function(e) {
9536
- // Check if ctrl or meta key is pressed also check if alt is false for Polish users
9537
- return (Env.mac ? e.metaKey : e.ctrlKey) && !e.altKey;
9699
+ // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
9700
+ return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
9538
9701
  }
9539
9702
  };
9540
9703
  });
@@ -10066,7 +10229,7 @@ define("tinymce/dom/ControlSelection", [
10066
10229
  // Included from: js/tinymce/classes/dom/RangeUtils.js
10067
10230
 
10068
10231
  /**
10069
- * Range.js
10232
+ * RangeUtils.js
10070
10233
  *
10071
10234
  * Copyright, Moxiecode Systems AB
10072
10235
  * Released under LGPL License.
@@ -10076,7 +10239,7 @@ define("tinymce/dom/ControlSelection", [
10076
10239
  */
10077
10240
 
10078
10241
  /**
10079
- * RangeUtils
10242
+ * This class contains a few utility methods for ranges.
10080
10243
  *
10081
10244
  * @class tinymce.dom.RangeUtils
10082
10245
  * @private
@@ -10087,6 +10250,20 @@ define("tinymce/dom/RangeUtils", [
10087
10250
  ], function(Tools, TreeWalker) {
10088
10251
  var each = Tools.each;
10089
10252
 
10253
+ function getEndChild(container, index) {
10254
+ var childNodes = container.childNodes;
10255
+
10256
+ index--;
10257
+
10258
+ if (index > childNodes.length - 1) {
10259
+ index = childNodes.length - 1;
10260
+ } else if (index < 0) {
10261
+ index = 0;
10262
+ }
10263
+
10264
+ return childNodes[index] || container;
10265
+ }
10266
+
10090
10267
  function RangeUtils(dom) {
10091
10268
  /**
10092
10269
  * Walks the specified range like object and executes the callback for each sibling collection it finds.
@@ -10199,7 +10376,7 @@ define("tinymce/dom/RangeUtils", [
10199
10376
 
10200
10377
  // If index based end position then resolve it
10201
10378
  if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
10202
- endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
10379
+ endContainer = getEndChild(endContainer, endOffset);
10203
10380
  }
10204
10381
 
10205
10382
  // Same container
@@ -10546,6 +10723,398 @@ define("tinymce/dom/RangeUtils", [
10546
10723
  return RangeUtils;
10547
10724
  });
10548
10725
 
10726
+ // Included from: js/tinymce/classes/dom/BookmarkManager.js
10727
+
10728
+ /**
10729
+ * BookmarkManager.js
10730
+ *
10731
+ * Copyright, Moxiecode Systems AB
10732
+ * Released under LGPL License.
10733
+ *
10734
+ * License: http://www.tinymce.com/license
10735
+ * Contributing: http://www.tinymce.com/contributing
10736
+ */
10737
+
10738
+ /**
10739
+ * This class handles selection bookmarks.
10740
+ *
10741
+ * @class tinymce.dom.BookmarkManager
10742
+ */
10743
+ define("tinymce/dom/BookmarkManager", [
10744
+ "tinymce/Env",
10745
+ "tinymce/util/Tools"
10746
+ ], function(Env, Tools) {
10747
+ /**
10748
+ * Constructs a new BookmarkManager instance for a specific selection instance.
10749
+ *
10750
+ * @constructor
10751
+ * @method BookmarkManager
10752
+ * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
10753
+ */
10754
+ function BookmarkManager(selection) {
10755
+ var dom = selection.dom;
10756
+
10757
+ /**
10758
+ * Returns a bookmark location for the current selection. This bookmark object
10759
+ * can then be used to restore the selection after some content modification to the document.
10760
+ *
10761
+ * @method getBookmark
10762
+ * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
10763
+ * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
10764
+ * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
10765
+ * @example
10766
+ * // Stores a bookmark of the current selection
10767
+ * var bm = tinymce.activeEditor.selection.getBookmark();
10768
+ *
10769
+ * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
10770
+ *
10771
+ * // Restore the selection bookmark
10772
+ * tinymce.activeEditor.selection.moveToBookmark(bm);
10773
+ */
10774
+ this.getBookmark = function(type, normalized) {
10775
+ var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
10776
+
10777
+ function findIndex(name, element) {
10778
+ var index = 0;
10779
+
10780
+ Tools.each(dom.select(name), function(node, i) {
10781
+ if (node == element) {
10782
+ index = i;
10783
+ }
10784
+ });
10785
+
10786
+ return index;
10787
+ }
10788
+
10789
+ function normalizeTableCellSelection(rng) {
10790
+ function moveEndPoint(start) {
10791
+ var container, offset, childNodes, prefix = start ? 'start' : 'end';
10792
+
10793
+ container = rng[prefix + 'Container'];
10794
+ offset = rng[prefix + 'Offset'];
10795
+
10796
+ if (container.nodeType == 1 && container.nodeName == "TR") {
10797
+ childNodes = container.childNodes;
10798
+ container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
10799
+ if (container) {
10800
+ offset = start ? 0 : container.childNodes.length;
10801
+ rng['set' + (start ? 'Start' : 'End')](container, offset);
10802
+ }
10803
+ }
10804
+ }
10805
+
10806
+ moveEndPoint(true);
10807
+ moveEndPoint();
10808
+
10809
+ return rng;
10810
+ }
10811
+
10812
+ function getLocation() {
10813
+ var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
10814
+
10815
+ function getPoint(rng, start) {
10816
+ var container = rng[start ? 'startContainer' : 'endContainer'],
10817
+ offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
10818
+
10819
+ if (container.nodeType == 3) {
10820
+ if (normalized) {
10821
+ for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
10822
+ offset += node.nodeValue.length;
10823
+ }
10824
+ }
10825
+
10826
+ point.push(offset);
10827
+ } else {
10828
+ childNodes = container.childNodes;
10829
+
10830
+ if (offset >= childNodes.length && childNodes.length) {
10831
+ after = 1;
10832
+ offset = Math.max(0, childNodes.length - 1);
10833
+ }
10834
+
10835
+ point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
10836
+ }
10837
+
10838
+ for (; container && container != root; container = container.parentNode) {
10839
+ point.push(dom.nodeIndex(container, normalized));
10840
+ }
10841
+
10842
+ return point;
10843
+ }
10844
+
10845
+ bookmark.start = getPoint(rng, true);
10846
+
10847
+ if (!selection.isCollapsed()) {
10848
+ bookmark.end = getPoint(rng);
10849
+ }
10850
+
10851
+ return bookmark;
10852
+ }
10853
+
10854
+ if (type == 2) {
10855
+ element = selection.getNode();
10856
+ name = element ? element.nodeName : null;
10857
+
10858
+ if (name == 'IMG') {
10859
+ return {name: name, index: findIndex(name, element)};
10860
+ }
10861
+
10862
+ if (selection.tridentSel) {
10863
+ return selection.tridentSel.getBookmark(type);
10864
+ }
10865
+
10866
+ return getLocation();
10867
+ }
10868
+
10869
+ // Handle simple range
10870
+ if (type) {
10871
+ return {rng: selection.getRng()};
10872
+ }
10873
+
10874
+ rng = selection.getRng();
10875
+ id = dom.uniqueId();
10876
+ collapsed = selection.isCollapsed();
10877
+ styles = 'overflow:hidden;line-height:0px';
10878
+
10879
+ // Explorer method
10880
+ if (rng.duplicate || rng.item) {
10881
+ // Text selection
10882
+ if (!rng.item) {
10883
+ rng2 = rng.duplicate();
10884
+
10885
+ try {
10886
+ // Insert start marker
10887
+ rng.collapse();
10888
+ rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
10889
+
10890
+ // Insert end marker
10891
+ if (!collapsed) {
10892
+ rng2.collapse(false);
10893
+
10894
+ // Detect the empty space after block elements in IE and move the
10895
+ // end back one character <p></p>] becomes <p>]</p>
10896
+ rng.moveToElementText(rng2.parentElement());
10897
+ if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
10898
+ rng2.move('character', -1);
10899
+ }
10900
+
10901
+ rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
10902
+ }
10903
+ } catch (ex) {
10904
+ // IE might throw unspecified error so lets ignore it
10905
+ return null;
10906
+ }
10907
+ } else {
10908
+ // Control selection
10909
+ element = rng.item(0);
10910
+ name = element.nodeName;
10911
+
10912
+ return {name: name, index: findIndex(name, element)};
10913
+ }
10914
+ } else {
10915
+ element = selection.getNode();
10916
+ name = element.nodeName;
10917
+ if (name == 'IMG') {
10918
+ return {name: name, index: findIndex(name, element)};
10919
+ }
10920
+
10921
+ // W3C method
10922
+ rng2 = normalizeTableCellSelection(rng.cloneRange());
10923
+
10924
+ // Insert end marker
10925
+ if (!collapsed) {
10926
+ rng2.collapse(false);
10927
+ rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
10928
+ }
10929
+
10930
+ rng = normalizeTableCellSelection(rng);
10931
+ rng.collapse(true);
10932
+ rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
10933
+ }
10934
+
10935
+ selection.moveToBookmark({id: id, keep: 1});
10936
+
10937
+ return {id: id};
10938
+ };
10939
+
10940
+ /**
10941
+ * Restores the selection to the specified bookmark.
10942
+ *
10943
+ * @method moveToBookmark
10944
+ * @param {Object} bookmark Bookmark to restore selection from.
10945
+ * @return {Boolean} true/false if it was successful or not.
10946
+ * @example
10947
+ * // Stores a bookmark of the current selection
10948
+ * var bm = tinymce.activeEditor.selection.getBookmark();
10949
+ *
10950
+ * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
10951
+ *
10952
+ * // Restore the selection bookmark
10953
+ * tinymce.activeEditor.selection.moveToBookmark(bm);
10954
+ */
10955
+ this.moveToBookmark = function(bookmark) {
10956
+ var rng, root, startContainer, endContainer, startOffset, endOffset;
10957
+
10958
+ function setEndPoint(start) {
10959
+ var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
10960
+
10961
+ if (point) {
10962
+ offset = point[0];
10963
+
10964
+ // Find container node
10965
+ for (node = root, i = point.length - 1; i >= 1; i--) {
10966
+ children = node.childNodes;
10967
+
10968
+ if (point[i] > children.length - 1) {
10969
+ return;
10970
+ }
10971
+
10972
+ node = children[point[i]];
10973
+ }
10974
+
10975
+ // Move text offset to best suitable location
10976
+ if (node.nodeType === 3) {
10977
+ offset = Math.min(point[0], node.nodeValue.length);
10978
+ }
10979
+
10980
+ // Move element offset to best suitable location
10981
+ if (node.nodeType === 1) {
10982
+ offset = Math.min(point[0], node.childNodes.length);
10983
+ }
10984
+
10985
+ // Set offset within container node
10986
+ if (start) {
10987
+ rng.setStart(node, offset);
10988
+ } else {
10989
+ rng.setEnd(node, offset);
10990
+ }
10991
+ }
10992
+
10993
+ return true;
10994
+ }
10995
+
10996
+ function restoreEndPoint(suffix) {
10997
+ var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
10998
+
10999
+ if (marker) {
11000
+ node = marker.parentNode;
11001
+
11002
+ if (suffix == 'start') {
11003
+ if (!keep) {
11004
+ idx = dom.nodeIndex(marker);
11005
+ } else {
11006
+ node = marker.firstChild;
11007
+ idx = 1;
11008
+ }
11009
+
11010
+ startContainer = endContainer = node;
11011
+ startOffset = endOffset = idx;
11012
+ } else {
11013
+ if (!keep) {
11014
+ idx = dom.nodeIndex(marker);
11015
+ } else {
11016
+ node = marker.firstChild;
11017
+ idx = 1;
11018
+ }
11019
+
11020
+ endContainer = node;
11021
+ endOffset = idx;
11022
+ }
11023
+
11024
+ if (!keep) {
11025
+ prev = marker.previousSibling;
11026
+ next = marker.nextSibling;
11027
+
11028
+ // Remove all marker text nodes
11029
+ Tools.each(Tools.grep(marker.childNodes), function(node) {
11030
+ if (node.nodeType == 3) {
11031
+ node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
11032
+ }
11033
+ });
11034
+
11035
+ // Remove marker but keep children if for example contents where inserted into the marker
11036
+ // Also remove duplicated instances of the marker for example by a
11037
+ // split operation or by WebKit auto split on paste feature
11038
+ while ((marker = dom.get(bookmark.id + '_' + suffix))) {
11039
+ dom.remove(marker, 1);
11040
+ }
11041
+
11042
+ // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
11043
+ // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
11044
+ // isn't worth the effort. Sorry, Opera but it's just a fact
11045
+ if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
11046
+ idx = prev.nodeValue.length;
11047
+ prev.appendData(next.nodeValue);
11048
+ dom.remove(next);
11049
+
11050
+ if (suffix == 'start') {
11051
+ startContainer = endContainer = prev;
11052
+ startOffset = endOffset = idx;
11053
+ } else {
11054
+ endContainer = prev;
11055
+ endOffset = idx;
11056
+ }
11057
+ }
11058
+ }
11059
+ }
11060
+ }
11061
+
11062
+ function addBogus(node) {
11063
+ // Adds a bogus BR element for empty block elements
11064
+ if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
11065
+ node.innerHTML = '<br data-mce-bogus="1" />';
11066
+ }
11067
+
11068
+ return node;
11069
+ }
11070
+
11071
+ if (bookmark) {
11072
+ if (bookmark.start) {
11073
+ rng = dom.createRng();
11074
+ root = dom.getRoot();
11075
+
11076
+ if (selection.tridentSel) {
11077
+ return selection.tridentSel.moveToBookmark(bookmark);
11078
+ }
11079
+
11080
+ if (setEndPoint(true) && setEndPoint()) {
11081
+ selection.setRng(rng);
11082
+ }
11083
+ } else if (bookmark.id) {
11084
+ // Restore start/end points
11085
+ restoreEndPoint('start');
11086
+ restoreEndPoint('end');
11087
+
11088
+ if (startContainer) {
11089
+ rng = dom.createRng();
11090
+ rng.setStart(addBogus(startContainer), startOffset);
11091
+ rng.setEnd(addBogus(endContainer), endOffset);
11092
+ selection.setRng(rng);
11093
+ }
11094
+ } else if (bookmark.name) {
11095
+ selection.select(dom.select(bookmark.name)[bookmark.index]);
11096
+ } else if (bookmark.rng) {
11097
+ selection.setRng(bookmark.rng);
11098
+ }
11099
+ }
11100
+ };
11101
+ }
11102
+
11103
+ /**
11104
+ * Returns true/false if the specified node is a bookmark node or not.
11105
+ *
11106
+ * @static
11107
+ * @method isBookmarkNode
11108
+ * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
11109
+ * @return {Boolean} true/false if the node is a bookmark node or not.
11110
+ */
11111
+ BookmarkManager.isBookmarkNode = function(node) {
11112
+ return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
11113
+ };
11114
+
11115
+ return BookmarkManager;
11116
+ });
11117
+
10549
11118
  // Included from: js/tinymce/classes/dom/Selection.js
10550
11119
 
10551
11120
  /**
@@ -10572,11 +11141,12 @@ define("tinymce/dom/Selection", [
10572
11141
  "tinymce/dom/TridentSelection",
10573
11142
  "tinymce/dom/ControlSelection",
10574
11143
  "tinymce/dom/RangeUtils",
11144
+ "tinymce/dom/BookmarkManager",
10575
11145
  "tinymce/Env",
10576
11146
  "tinymce/util/Tools"
10577
- ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, Env, Tools) {
10578
- var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
10579
- var isIE = Env.ie, isOpera = Env.opera;
11147
+ ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) {
11148
+ var each = Tools.each, trim = Tools.trim;
11149
+ var isIE = Env.ie;
10580
11150
 
10581
11151
  /**
10582
11152
  * Constructs a new selection instance.
@@ -10594,7 +11164,7 @@ define("tinymce/dom/Selection", [
10594
11164
  self.win = win;
10595
11165
  self.serializer = serializer;
10596
11166
  self.editor = editor;
10597
-
11167
+ self.bookmarkManager = new BookmarkManager(self);
10598
11168
  self.controlSelection = new ControlSelection(self, editor);
10599
11169
 
10600
11170
  // No W3C Range support
@@ -10894,169 +11464,7 @@ define("tinymce/dom/Selection", [
10894
11464
  * tinymce.activeEditor.selection.moveToBookmark(bm);
10895
11465
  */
10896
11466
  getBookmark: function(type, normalized) {
10897
- var self = this, dom = self.dom, rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
10898
-
10899
- function findIndex(name, element) {
10900
- var index = 0;
10901
-
10902
- each(dom.select(name), function(node, i) {
10903
- if (node == element) {
10904
- index = i;
10905
- }
10906
- });
10907
-
10908
- return index;
10909
- }
10910
-
10911
- function normalizeTableCellSelection(rng) {
10912
- function moveEndPoint(start) {
10913
- var container, offset, childNodes, prefix = start ? 'start' : 'end';
10914
-
10915
- container = rng[prefix + 'Container'];
10916
- offset = rng[prefix + 'Offset'];
10917
-
10918
- if (container.nodeType == 1 && container.nodeName == "TR") {
10919
- childNodes = container.childNodes;
10920
- container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
10921
- if (container) {
10922
- offset = start ? 0 : container.childNodes.length;
10923
- rng['set' + (start ? 'Start' : 'End')](container, offset);
10924
- }
10925
- }
10926
- }
10927
-
10928
- moveEndPoint(true);
10929
- moveEndPoint();
10930
-
10931
- return rng;
10932
- }
10933
-
10934
- function getLocation() {
10935
- var rng = self.getRng(true), root = dom.getRoot(), bookmark = {};
10936
-
10937
- function getPoint(rng, start) {
10938
- var container = rng[start ? 'startContainer' : 'endContainer'],
10939
- offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
10940
-
10941
- if (container.nodeType == 3) {
10942
- if (normalized) {
10943
- for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
10944
- offset += node.nodeValue.length;
10945
- }
10946
- }
10947
-
10948
- point.push(offset);
10949
- } else {
10950
- childNodes = container.childNodes;
10951
-
10952
- if (offset >= childNodes.length && childNodes.length) {
10953
- after = 1;
10954
- offset = Math.max(0, childNodes.length - 1);
10955
- }
10956
-
10957
- point.push(self.dom.nodeIndex(childNodes[offset], normalized) + after);
10958
- }
10959
-
10960
- for (; container && container != root; container = container.parentNode) {
10961
- point.push(self.dom.nodeIndex(container, normalized));
10962
- }
10963
-
10964
- return point;
10965
- }
10966
-
10967
- bookmark.start = getPoint(rng, true);
10968
-
10969
- if (!self.isCollapsed()) {
10970
- bookmark.end = getPoint(rng);
10971
- }
10972
-
10973
- return bookmark;
10974
- }
10975
-
10976
- if (type == 2) {
10977
- element = self.getNode();
10978
- name = element ? element.nodeName : null;
10979
-
10980
- if (name == 'IMG') {
10981
- return {name: name, index: findIndex(name, element)};
10982
- }
10983
-
10984
- if (self.tridentSel) {
10985
- return self.tridentSel.getBookmark(type);
10986
- }
10987
-
10988
- return getLocation();
10989
- }
10990
-
10991
- // Handle simple range
10992
- if (type) {
10993
- return {rng: self.getRng()};
10994
- }
10995
-
10996
- rng = self.getRng();
10997
- id = dom.uniqueId();
10998
- collapsed = self.isCollapsed();
10999
- styles = 'overflow:hidden;line-height:0px';
11000
-
11001
- // Explorer method
11002
- if (rng.duplicate || rng.item) {
11003
- // Text selection
11004
- if (!rng.item) {
11005
- rng2 = rng.duplicate();
11006
-
11007
- try {
11008
- // Insert start marker
11009
- rng.collapse();
11010
- rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
11011
-
11012
- // Insert end marker
11013
- if (!collapsed) {
11014
- rng2.collapse(false);
11015
-
11016
- // Detect the empty space after block elements in IE and move the
11017
- // end back one character <p></p>] becomes <p>]</p>
11018
- rng.moveToElementText(rng2.parentElement());
11019
- if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
11020
- rng2.move('character', -1);
11021
- }
11022
-
11023
- rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
11024
- }
11025
- } catch (ex) {
11026
- // IE might throw unspecified error so lets ignore it
11027
- return null;
11028
- }
11029
- } else {
11030
- // Control selection
11031
- element = rng.item(0);
11032
- name = element.nodeName;
11033
-
11034
- return {name: name, index: findIndex(name, element)};
11035
- }
11036
- } else {
11037
- element = self.getNode();
11038
- name = element.nodeName;
11039
- if (name == 'IMG') {
11040
- return {name: name, index: findIndex(name, element)};
11041
- }
11042
-
11043
- // W3C method
11044
- rng2 = normalizeTableCellSelection(rng.cloneRange());
11045
-
11046
- // Insert end marker
11047
- if (!collapsed) {
11048
- rng2.collapse(false);
11049
- rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
11050
- }
11051
-
11052
- rng = normalizeTableCellSelection(rng);
11053
- rng.collapse(true);
11054
- rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
11055
- }
11056
-
11057
- self.moveToBookmark({id: id, keep: 1});
11058
-
11059
- return {id: id};
11467
+ return this.bookmarkManager.getBookmark(type, normalized);
11060
11468
  },
11061
11469
 
11062
11470
  /**
@@ -11075,150 +11483,7 @@ define("tinymce/dom/Selection", [
11075
11483
  * tinymce.activeEditor.selection.moveToBookmark(bm);
11076
11484
  */
11077
11485
  moveToBookmark: function(bookmark) {
11078
- var self = this, dom = self.dom, rng, root, startContainer, endContainer, startOffset, endOffset;
11079
-
11080
- function setEndPoint(start) {
11081
- var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
11082
-
11083
- if (point) {
11084
- offset = point[0];
11085
-
11086
- // Find container node
11087
- for (node = root, i = point.length - 1; i >= 1; i--) {
11088
- children = node.childNodes;
11089
-
11090
- if (point[i] > children.length - 1) {
11091
- return;
11092
- }
11093
-
11094
- node = children[point[i]];
11095
- }
11096
-
11097
- // Move text offset to best suitable location
11098
- if (node.nodeType === 3) {
11099
- offset = Math.min(point[0], node.nodeValue.length);
11100
- }
11101
-
11102
- // Move element offset to best suitable location
11103
- if (node.nodeType === 1) {
11104
- offset = Math.min(point[0], node.childNodes.length);
11105
- }
11106
-
11107
- // Set offset within container node
11108
- if (start) {
11109
- rng.setStart(node, offset);
11110
- } else {
11111
- rng.setEnd(node, offset);
11112
- }
11113
- }
11114
-
11115
- return true;
11116
- }
11117
-
11118
- function restoreEndPoint(suffix) {
11119
- var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
11120
-
11121
- if (marker) {
11122
- node = marker.parentNode;
11123
-
11124
- if (suffix == 'start') {
11125
- if (!keep) {
11126
- idx = dom.nodeIndex(marker);
11127
- } else {
11128
- node = marker.firstChild;
11129
- idx = 1;
11130
- }
11131
-
11132
- startContainer = endContainer = node;
11133
- startOffset = endOffset = idx;
11134
- } else {
11135
- if (!keep) {
11136
- idx = dom.nodeIndex(marker);
11137
- } else {
11138
- node = marker.firstChild;
11139
- idx = 1;
11140
- }
11141
-
11142
- endContainer = node;
11143
- endOffset = idx;
11144
- }
11145
-
11146
- if (!keep) {
11147
- prev = marker.previousSibling;
11148
- next = marker.nextSibling;
11149
-
11150
- // Remove all marker text nodes
11151
- each(grep(marker.childNodes), function(node) {
11152
- if (node.nodeType == 3) {
11153
- node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
11154
- }
11155
- });
11156
-
11157
- // Remove marker but keep children if for example contents where inserted into the marker
11158
- // Also remove duplicated instances of the marker for example by a
11159
- // split operation or by WebKit auto split on paste feature
11160
- while ((marker = dom.get(bookmark.id + '_' + suffix))) {
11161
- dom.remove(marker, 1);
11162
- }
11163
-
11164
- // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
11165
- // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
11166
- // isn't worth the effort. Sorry, Opera but it's just a fact
11167
- if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) {
11168
- idx = prev.nodeValue.length;
11169
- prev.appendData(next.nodeValue);
11170
- dom.remove(next);
11171
-
11172
- if (suffix == 'start') {
11173
- startContainer = endContainer = prev;
11174
- startOffset = endOffset = idx;
11175
- } else {
11176
- endContainer = prev;
11177
- endOffset = idx;
11178
- }
11179
- }
11180
- }
11181
- }
11182
- }
11183
-
11184
- function addBogus(node) {
11185
- // Adds a bogus BR element for empty block elements
11186
- if (dom.isBlock(node) && !node.innerHTML && !isIE) {
11187
- node.innerHTML = '<br data-mce-bogus="1" />';
11188
- }
11189
-
11190
- return node;
11191
- }
11192
-
11193
- if (bookmark) {
11194
- if (bookmark.start) {
11195
- rng = dom.createRng();
11196
- root = dom.getRoot();
11197
-
11198
- if (self.tridentSel) {
11199
- return self.tridentSel.moveToBookmark(bookmark);
11200
- }
11201
-
11202
- if (setEndPoint(true) && setEndPoint()) {
11203
- self.setRng(rng);
11204
- }
11205
- } else if (bookmark.id) {
11206
- // Restore start/end points
11207
- restoreEndPoint('start');
11208
- restoreEndPoint('end');
11209
-
11210
- if (startContainer) {
11211
- rng = dom.createRng();
11212
- rng.setStart(addBogus(startContainer), startOffset);
11213
- rng.setEnd(addBogus(endContainer), endOffset);
11214
- self.setRng(rng);
11215
- }
11216
- } else if (bookmark.name) {
11217
- self.select(dom.select(bookmark.name)[bookmark.index]);
11218
- } else if (bookmark.rng) {
11219
- self.setRng(bookmark.rng);
11220
- }
11221
- }
11486
+ return this.bookmarkManager.moveToBookmark(bookmark);
11222
11487
  },
11223
11488
 
11224
11489
  /**
@@ -11808,6 +12073,126 @@ define("tinymce/dom/Selection", [
11808
12073
  return Selection;
11809
12074
  });
11810
12075
 
12076
+ // Included from: js/tinymce/classes/dom/ElementUtils.js
12077
+
12078
+ /**
12079
+ * ElementUtils.js
12080
+ *
12081
+ * Copyright, Moxiecode Systems AB
12082
+ * Released under LGPL License.
12083
+ *
12084
+ * License: http://www.tinymce.com/license
12085
+ * Contributing: http://www.tinymce.com/contributing
12086
+ */
12087
+
12088
+ /**
12089
+ * Utility class for various element specific functions.
12090
+ *
12091
+ * @private
12092
+ */
12093
+ define("tinymce/dom/ElementUtils", [
12094
+ "tinymce/dom/BookmarkManager",
12095
+ "tinymce/util/Tools"
12096
+ ], function(BookmarkManager, Tools) {
12097
+ var each = Tools.each;
12098
+
12099
+ function ElementUtils(dom) {
12100
+ /**
12101
+ * Compares two nodes and checks if it's attributes and styles matches.
12102
+ * This doesn't compare classes as items since their order is significant.
12103
+ *
12104
+ * @method compare
12105
+ * @param {Node} node1 First node to compare with.
12106
+ * @param {Node} node2 Second node to compare with.
12107
+ * @return {boolean} True/false if the nodes are the same or not.
12108
+ */
12109
+ this.compare = function(node1, node2) {
12110
+ // Not the same name
12111
+ if (node1.nodeName != node2.nodeName) {
12112
+ return false;
12113
+ }
12114
+
12115
+ /**
12116
+ * Returns all the nodes attributes excluding internal ones, styles and classes.
12117
+ *
12118
+ * @private
12119
+ * @param {Node} node Node to get attributes from.
12120
+ * @return {Object} Name/value object with attributes and attribute values.
12121
+ */
12122
+ function getAttribs(node) {
12123
+ var attribs = {};
12124
+
12125
+ each(dom.getAttribs(node), function(attr) {
12126
+ var name = attr.nodeName.toLowerCase();
12127
+
12128
+ // Don't compare internal attributes or style
12129
+ if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
12130
+ attribs[name] = dom.getAttrib(node, name);
12131
+ }
12132
+ });
12133
+
12134
+ return attribs;
12135
+ }
12136
+
12137
+ /**
12138
+ * Compares two objects checks if it's key + value exists in the other one.
12139
+ *
12140
+ * @private
12141
+ * @param {Object} obj1 First object to compare.
12142
+ * @param {Object} obj2 Second object to compare.
12143
+ * @return {boolean} True/false if the objects matches or not.
12144
+ */
12145
+ function compareObjects(obj1, obj2) {
12146
+ var value, name;
12147
+
12148
+ for (name in obj1) {
12149
+ // Obj1 has item obj2 doesn't have
12150
+ if (obj1.hasOwnProperty(name)) {
12151
+ value = obj2[name];
12152
+
12153
+ // Obj2 doesn't have obj1 item
12154
+ if (typeof value == "undefined") {
12155
+ return false;
12156
+ }
12157
+
12158
+ // Obj2 item has a different value
12159
+ if (obj1[name] != value) {
12160
+ return false;
12161
+ }
12162
+
12163
+ // Delete similar value
12164
+ delete obj2[name];
12165
+ }
12166
+ }
12167
+
12168
+ // Check if obj 2 has something obj 1 doesn't have
12169
+ for (name in obj2) {
12170
+ // Obj2 has item obj1 doesn't have
12171
+ if (obj2.hasOwnProperty(name)) {
12172
+ return false;
12173
+ }
12174
+ }
12175
+
12176
+ return true;
12177
+ }
12178
+
12179
+ // Attribs are not the same
12180
+ if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
12181
+ return false;
12182
+ }
12183
+
12184
+ // Styles are not the same
12185
+ if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
12186
+ return false;
12187
+ }
12188
+
12189
+ return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
12190
+ };
12191
+ }
12192
+
12193
+ return ElementUtils;
12194
+ });
12195
+
11811
12196
  // Included from: js/tinymce/classes/fmt/Preview.js
11812
12197
 
11813
12198
  /**
@@ -11991,9 +12376,11 @@ define("tinymce/fmt/Preview", [
11991
12376
  define("tinymce/Formatter", [
11992
12377
  "tinymce/dom/TreeWalker",
11993
12378
  "tinymce/dom/RangeUtils",
12379
+ "tinymce/dom/BookmarkManager",
12380
+ "tinymce/dom/ElementUtils",
11994
12381
  "tinymce/util/Tools",
11995
12382
  "tinymce/fmt/Preview"
11996
- ], function(TreeWalker, RangeUtils, Tools, Preview) {
12383
+ ], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) {
11997
12384
  /**
11998
12385
  * Constructs a new formatter instance.
11999
12386
  *
@@ -12017,7 +12404,8 @@ define("tinymce/Formatter", [
12017
12404
  undef,
12018
12405
  getContentEditable = dom.getContentEditable,
12019
12406
  disableCaretContainer,
12020
- markCaretContainersBogus;
12407
+ markCaretContainersBogus,
12408
+ isBookmarkNode = BookmarkManager.isBookmarkNode;
12021
12409
 
12022
12410
  var each = Tools.each,
12023
12411
  grep = Tools.grep,
@@ -12042,7 +12430,6 @@ define("tinymce/Formatter", [
12042
12430
 
12043
12431
  function defaultFormats() {
12044
12432
  register({
12045
-
12046
12433
  valigntop: [
12047
12434
  {selector: 'td,th', styles: {'verticalAlign': 'top'}}
12048
12435
  ],
@@ -12333,7 +12720,7 @@ define("tinymce/Formatter", [
12333
12720
 
12334
12721
  // get the index of the bookmarks
12335
12722
  each(node.childNodes, function(n, index) {
12336
- if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
12723
+ if (isBookmarkNode(n)) {
12337
12724
  if (n.id == bookmark.id + "_start") {
12338
12725
  startIndex = index;
12339
12726
  } else if (n.id == bookmark.id + "_end") {
@@ -13903,21 +14290,10 @@ define("tinymce/Formatter", [
13903
14290
 
13904
14291
  for (node = inc ? node : node[next]; node; node = node[next]) {
13905
14292
  if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
13906
- return node;
13907
- }
13908
- }
13909
- }
13910
- }
13911
-
13912
- /**
13913
- * Checks if the specified node is a bookmark node or not.
13914
- *
13915
- * @private
13916
- * @param {Node} node Node to check if it's a bookmark node or not.
13917
- * @return {Boolean} true/false if the node is a bookmark node.
13918
- */
13919
- function isBookmarkNode(node) {
13920
- return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
14293
+ return node;
14294
+ }
14295
+ }
14296
+ }
13921
14297
  }
13922
14298
 
13923
14299
  /**
@@ -13929,99 +14305,7 @@ define("tinymce/Formatter", [
13929
14305
  * @return {Node} Next node if we didn't merge and prev node if we did.
13930
14306
  */
13931
14307
  function mergeSiblings(prev, next) {
13932
- var sibling, tmpSibling;
13933
-
13934
- /**
13935
- * Compares two nodes and checks if it's attributes and styles matches.
13936
- * This doesn't compare classes as items since their order is significant.
13937
- *
13938
- * @private
13939
- * @param {Node} node1 First node to compare with.
13940
- * @param {Node} node2 Second node to compare with.
13941
- * @return {boolean} True/false if the nodes are the same or not.
13942
- */
13943
- function compareElements(node1, node2) {
13944
- // Not the same name
13945
- if (node1.nodeName != node2.nodeName) {
13946
- return FALSE;
13947
- }
13948
-
13949
- /**
13950
- * Returns all the nodes attributes excluding internal ones, styles and classes.
13951
- *
13952
- * @private
13953
- * @param {Node} node Node to get attributes from.
13954
- * @return {Object} Name/value object with attributes and attribute values.
13955
- */
13956
- function getAttribs(node) {
13957
- var attribs = {};
13958
-
13959
- each(dom.getAttribs(node), function(attr) {
13960
- var name = attr.nodeName.toLowerCase();
13961
-
13962
- // Don't compare internal attributes or style
13963
- if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
13964
- attribs[name] = dom.getAttrib(node, name);
13965
- }
13966
- });
13967
-
13968
- return attribs;
13969
- }
13970
-
13971
- /**
13972
- * Compares two objects checks if it's key + value exists in the other one.
13973
- *
13974
- * @private
13975
- * @param {Object} obj1 First object to compare.
13976
- * @param {Object} obj2 Second object to compare.
13977
- * @return {boolean} True/false if the objects matches or not.
13978
- */
13979
- function compareObjects(obj1, obj2) {
13980
- var value, name;
13981
-
13982
- for (name in obj1) {
13983
- // Obj1 has item obj2 doesn't have
13984
- if (obj1.hasOwnProperty(name)) {
13985
- value = obj2[name];
13986
-
13987
- // Obj2 doesn't have obj1 item
13988
- if (value === undef) {
13989
- return FALSE;
13990
- }
13991
-
13992
- // Obj2 item has a different value
13993
- if (obj1[name] != value) {
13994
- return FALSE;
13995
- }
13996
-
13997
- // Delete similar value
13998
- delete obj2[name];
13999
- }
14000
- }
14001
-
14002
- // Check if obj 2 has something obj 1 doesn't have
14003
- for (name in obj2) {
14004
- // Obj2 has item obj1 doesn't have
14005
- if (obj2.hasOwnProperty(name)) {
14006
- return FALSE;
14007
- }
14008
- }
14009
-
14010
- return TRUE;
14011
- }
14012
-
14013
- // Attribs are not the same
14014
- if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
14015
- return FALSE;
14016
- }
14017
-
14018
- // Styles are not the same
14019
- if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
14020
- return FALSE;
14021
- }
14022
-
14023
- return !isBookmarkNode(node1) && !isBookmarkNode(node2);
14024
- }
14308
+ var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
14025
14309
 
14026
14310
  function findElementSibling(node, sibling_name) {
14027
14311
  for (sibling = node; sibling; sibling = sibling[sibling_name]) {
@@ -14044,7 +14328,7 @@ define("tinymce/Formatter", [
14044
14328
  next = findElementSibling(next, 'nextSibling');
14045
14329
 
14046
14330
  // Compare next and previous nodes
14047
- if (compareElements(prev, next)) {
14331
+ if (elementUtils.compare(prev, next)) {
14048
14332
  // Append nodes between
14049
14333
  for (sibling = prev.nextSibling; sibling && sibling != next;) {
14050
14334
  tmpSibling = sibling;
@@ -14247,7 +14531,7 @@ define("tinymce/Formatter", [
14247
14531
  node = container;
14248
14532
 
14249
14533
  if (container.nodeType == 3) {
14250
- if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
14534
+ if (offset != container.nodeValue.length) {
14251
14535
  hasContentAfter = true;
14252
14536
  }
14253
14537
 
@@ -14829,6 +15113,10 @@ define("tinymce/EnterKey", [
14829
15113
  function trimInlineElementsOnLeftSideOfBlock(block) {
14830
15114
  var node = block, firstChilds = [], i;
14831
15115
 
15116
+ if (!node) {
15117
+ return;
15118
+ }
15119
+
14832
15120
  // Find inner most first child ex: <p><i><b>*</b></i></p>
14833
15121
  while ((node = node.firstChild)) {
14834
15122
  if (dom.isBlock(node)) {
@@ -14869,6 +15157,10 @@ define("tinymce/EnterKey", [
14869
15157
  }
14870
15158
  }
14871
15159
 
15160
+ if (!root) {
15161
+ return;
15162
+ }
15163
+
14872
15164
  // Old IE versions doesn't properly render blocks with br elements in them
14873
15165
  // For example <p><br></p> wont be rendered correctly in a contentEditable area
14874
15166
  // until you remove the br producing <p></p>
@@ -14878,16 +15170,23 @@ define("tinymce/EnterKey", [
14878
15170
  }
14879
15171
  }
14880
15172
 
14881
- if (root.nodeName == 'LI') {
15173
+ if (/^(LI|DT|DD)$/.test(root.nodeName)) {
14882
15174
  var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
14883
15175
 
14884
- if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) {
15176
+ if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
14885
15177
  root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
14886
15178
  }
14887
15179
  }
14888
15180
 
14889
15181
  rng = dom.createRng();
14890
15182
 
15183
+ // Normalize whitespace to remove empty text nodes. Fix for: #6904
15184
+ // Gecko will be able to place the caret in empty text nodes but it won't render propery
15185
+ // Older IE versions will sometimes crash so for now ignore all IE versions
15186
+ if (!Env.ie) {
15187
+ root.normalize();
15188
+ }
15189
+
14891
15190
  if (root.hasChildNodes()) {
14892
15191
  walker = new TreeWalker(root, root);
14893
15192
 
@@ -14951,7 +15250,7 @@ define("tinymce/EnterKey", [
14951
15250
  // Creates a new block element by cloning the current one or creating a new one if the name is specified
14952
15251
  // This function will also copy any text formatting from the parent block and add it to the new one
14953
15252
  function createNewBlock(name) {
14954
- var node = container, block, clonedNode, caretNode;
15253
+ var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
14955
15254
 
14956
15255
  if (name || parentBlockName == "TABLE") {
14957
15256
  block = dom.create(name || newBlockName);
@@ -14965,7 +15264,7 @@ define("tinymce/EnterKey", [
14965
15264
  // Clone any parent styles
14966
15265
  if (settings.keep_styles !== false) {
14967
15266
  do {
14968
- if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U|VAR|CITE|DFN|CODE|MARK|Q|SUP|SUB|SAMP)$/.test(node.nodeName)) {
15267
+ if (textInlineElements[node.nodeName]) {
14969
15268
  // Never clone a caret containers
14970
15269
  if (node.id == '_mce_caret') {
14971
15270
  continue;
@@ -15126,7 +15425,7 @@ define("tinymce/EnterKey", [
15126
15425
  function getContainerBlock() {
15127
15426
  var containerBlockParent = containerBlock.parentNode;
15128
15427
 
15129
- if (containerBlockParent.nodeName == 'LI') {
15428
+ if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
15130
15429
  return containerBlockParent;
15131
15430
  }
15132
15431
 
@@ -15356,8 +15655,8 @@ define("tinymce/EnterKey", [
15356
15655
  parentBlockName = containerBlockName;
15357
15656
  }
15358
15657
 
15359
- // Handle enter in LI
15360
- if (parentBlockName == 'LI') {
15658
+ // Handle enter in list item
15659
+ if (/^(LI|DT|DD)$/.test(parentBlockName)) {
15361
15660
  if (!newBlockName && shiftKey) {
15362
15661
  insertBr();
15363
15662
  return;
@@ -15602,8 +15901,9 @@ define("tinymce/ForceBlocks", [], function() {
15602
15901
  define("tinymce/EditorCommands", [
15603
15902
  "tinymce/html/Serializer",
15604
15903
  "tinymce/Env",
15605
- "tinymce/util/Tools"
15606
- ], function(Serializer, Env, Tools) {
15904
+ "tinymce/util/Tools",
15905
+ "tinymce/dom/ElementUtils"
15906
+ ], function(Serializer, Env, Tools, ElementUtils) {
15607
15907
  // Added for compression purposes
15608
15908
  var each = Tools.each, extend = Tools.extend;
15609
15909
  var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
@@ -15898,7 +16198,8 @@ define("tinymce/EditorCommands", [
15898
16198
 
15899
16199
  mceInsertContent: function(command, ui, value) {
15900
16200
  var parser, serializer, parentNode, rootNode, fragment, args;
15901
- var marker, rng, node, node2, bookmarkHtml;
16201
+ var marker, rng, node, node2, bookmarkHtml, merge;
16202
+ var textInlineElements = editor.schema.getTextInlineElements();
15902
16203
 
15903
16204
  function trimOrPaddLeftRight(html) {
15904
16205
  var rng, container, offset;
@@ -15928,6 +16229,37 @@ define("tinymce/EditorCommands", [
15928
16229
  return html;
15929
16230
  }
15930
16231
 
16232
+ function markInlineFormatElements(fragment) {
16233
+ if (merge) {
16234
+ for (node = fragment.firstChild; node; node = node.walk(true)) {
16235
+ if (textInlineElements[node.name]) {
16236
+ node.attr('data-mce-new', "true");
16237
+ }
16238
+ }
16239
+ }
16240
+ }
16241
+
16242
+ function reduceInlineTextElements() {
16243
+ if (merge) {
16244
+ var root = editor.getBody(), elementUtils = new ElementUtils(dom);
16245
+
16246
+ each(dom.select('*[data-mce-new]'), function(node) {
16247
+ node.removeAttribute('data-mce-new');
16248
+
16249
+ for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
16250
+ if (elementUtils.compare(testNode, node)) {
16251
+ dom.remove(node, true);
16252
+ }
16253
+ }
16254
+ });
16255
+ }
16256
+ }
16257
+
16258
+ if (typeof(value) != 'string') {
16259
+ merge = value.merge;
16260
+ value = value.content;
16261
+ }
16262
+
15931
16263
  // Check for whitespace before/after value
15932
16264
  if (/^ | $/.test(value)) {
15933
16265
  value = trimOrPaddLeftRight(value);
@@ -15975,6 +16307,8 @@ define("tinymce/EditorCommands", [
15975
16307
  var parserArgs = {context: parentNode.nodeName.toLowerCase()};
15976
16308
  fragment = parser.parse(value, parserArgs);
15977
16309
 
16310
+ markInlineFormatElements(fragment);
16311
+
15978
16312
  // Move the caret to a more suitable location
15979
16313
  node = fragment.lastChild;
15980
16314
  if (node.attr('id') == 'mce_marker') {
@@ -16041,6 +16375,8 @@ define("tinymce/EditorCommands", [
16041
16375
  }
16042
16376
  }
16043
16377
 
16378
+ reduceInlineTextElements();
16379
+
16044
16380
  marker = dom.get('mce_marker');
16045
16381
  selection.scrollIntoView(marker);
16046
16382
 
@@ -16343,11 +16679,9 @@ define("tinymce/util/URI", [
16343
16679
  function URI(url, settings) {
16344
16680
  var self = this, baseUri, base_url;
16345
16681
 
16346
- // Trim whitespace
16347
16682
  url = trim(url);
16348
-
16349
- // Default settings
16350
16683
  settings = self.settings = settings || {};
16684
+ baseUri = settings.base_uri;
16351
16685
 
16352
16686
  // Strange app protocol that isn't http/https or local anchor
16353
16687
  // For example: mailto,skype,tel etc.
@@ -16360,7 +16694,7 @@ define("tinymce/util/URI", [
16360
16694
 
16361
16695
  // Absolute path with no host, fake host and protocol
16362
16696
  if (url.indexOf('/') === 0 && !isProtocolRelative) {
16363
- url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url;
16697
+ url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
16364
16698
  }
16365
16699
 
16366
16700
  // Relative path http:// or protocol relative //path
@@ -16369,7 +16703,8 @@ define("tinymce/util/URI", [
16369
16703
  if (settings.base_uri.protocol === "") {
16370
16704
  url = '//mce_host' + self.toAbsPath(base_url, url);
16371
16705
  } else {
16372
- url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url);
16706
+ url = /([^#?]*)([#?]?.*)/.exec(url);
16707
+ url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
16373
16708
  }
16374
16709
  }
16375
16710
 
@@ -16391,7 +16726,6 @@ define("tinymce/util/URI", [
16391
16726
  self[v] = part;
16392
16727
  });
16393
16728
 
16394
- baseUri = settings.base_uri;
16395
16729
  if (baseUri) {
16396
16730
  if (!self.protocol) {
16397
16731
  self.protocol = baseUri.protocol;
@@ -16901,7 +17235,8 @@ define("tinymce/util/EventDispatcher", [
16901
17235
  var nativeEvents = Tools.makeMap(
16902
17236
  "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
16903
17237
  "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
16904
- "draggesture dragdrop drop drag submit",
17238
+ "draggesture dragdrop drop drag submit " +
17239
+ "compositionstart compositionend compositionupdate",
16905
17240
  ' '
16906
17241
  );
16907
17242
 
@@ -17958,9 +18293,11 @@ define("tinymce/ui/DomUtils", [
17958
18293
  ], function(Tools, DOMUtils) {
17959
18294
  "use strict";
17960
18295
 
18296
+ var count = 0;
18297
+
17961
18298
  return {
17962
18299
  id: function() {
17963
- return DOMUtils.DOM.uniqueId();
18300
+ return 'mceu_' + (count++);
17964
18301
  },
17965
18302
 
17966
18303
  createFragment: function(html) {
@@ -21194,9 +21531,117 @@ define("tinymce/ui/FloatPanel", [
21194
21531
  ], function(Panel, Movable, Resizable, DomUtils) {
21195
21532
  "use strict";
21196
21533
 
21197
- var documentClickHandler, documentScrollHandler, visiblePanels = [];
21534
+ var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
21198
21535
  var zOrder = [], hasModal;
21199
21536
 
21537
+ function bindDocumentClickHandler() {
21538
+ function isChildOf(ctrl, parent) {
21539
+ while (ctrl) {
21540
+ if (ctrl == parent) {
21541
+ return true;
21542
+ }
21543
+
21544
+ ctrl = ctrl.parent();
21545
+ }
21546
+ }
21547
+
21548
+ if (!documentClickHandler) {
21549
+ documentClickHandler = function(e) {
21550
+ // Gecko fires click event and in the wrong order on Mac so lets normalize
21551
+ if (e.button == 2) {
21552
+ return;
21553
+ }
21554
+
21555
+ // Hide any float panel when a click is out side that float panel and the
21556
+ // float panels direct parent for example a click on a menu button
21557
+ var i = visiblePanels.length;
21558
+ while (i--) {
21559
+ var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
21560
+
21561
+ if (panel.settings.autohide) {
21562
+ if (clickCtrl) {
21563
+ if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
21564
+ continue;
21565
+ }
21566
+ }
21567
+
21568
+ e = panel.fire('autohide', {target: e.target});
21569
+ if (!e.isDefaultPrevented()) {
21570
+ panel.hide();
21571
+ }
21572
+ }
21573
+ }
21574
+ };
21575
+
21576
+ DomUtils.on(document, 'click', documentClickHandler);
21577
+ }
21578
+ }
21579
+
21580
+ function bindDocumentScrollHandler() {
21581
+ if (!documentScrollHandler) {
21582
+ documentScrollHandler = function() {
21583
+ var i;
21584
+
21585
+ i = visiblePanels.length;
21586
+ while (i--) {
21587
+ repositionPanel(visiblePanels[i]);
21588
+ }
21589
+ };
21590
+
21591
+ DomUtils.on(window, 'scroll', documentScrollHandler);
21592
+ }
21593
+ }
21594
+
21595
+ function bindWindowResizeHandler() {
21596
+ if (!windowResizeHandler) {
21597
+ windowResizeHandler = function() {
21598
+ FloatPanel.hideAll();
21599
+ };
21600
+
21601
+ DomUtils.on(window, 'resize', windowResizeHandler);
21602
+ }
21603
+ }
21604
+
21605
+ /**
21606
+ * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
21607
+ * also reposition all child panels of the current panel.
21608
+ */
21609
+ function repositionPanel(panel) {
21610
+ var scrollY = DomUtils.getViewPort().y;
21611
+
21612
+ function toggleFixedChildPanels(fixed, deltaY) {
21613
+ var parent;
21614
+
21615
+ for (var i = 0; i < visiblePanels.length; i++) {
21616
+ if (visiblePanels[i] != panel) {
21617
+ parent = visiblePanels[i].parent();
21618
+
21619
+ while (parent && (parent = parent.parent())) {
21620
+ if (parent == panel) {
21621
+ visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
21622
+ }
21623
+ }
21624
+ }
21625
+ }
21626
+ }
21627
+
21628
+ if (panel.settings.autofix) {
21629
+ if (!panel._fixed) {
21630
+ panel._autoFixY = panel.layoutRect().y;
21631
+
21632
+ if (panel._autoFixY < scrollY) {
21633
+ panel.fixed(true).layoutRect({y: 0}).repaint();
21634
+ toggleFixedChildPanels(true, scrollY - panel._autoFixY);
21635
+ }
21636
+ } else {
21637
+ if (panel._autoFixY > scrollY) {
21638
+ panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
21639
+ toggleFixedChildPanels(false, panel._autoFixY - scrollY);
21640
+ }
21641
+ }
21642
+ }
21643
+ }
21644
+
21200
21645
  var FloatPanel = Panel.extend({
21201
21646
  Mixins: [Movable, Resizable],
21202
21647
 
@@ -21238,56 +21683,6 @@ define("tinymce/ui/FloatPanel", [
21238
21683
  FloatPanel.currentZIndex = zIndex;
21239
21684
  }
21240
21685
 
21241
- function isChildOf(ctrl, parent) {
21242
- while (ctrl) {
21243
- if (ctrl == parent) {
21244
- return true;
21245
- }
21246
-
21247
- ctrl = ctrl.parent();
21248
- }
21249
- }
21250
-
21251
- /**
21252
- * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
21253
- * also reposition all child panels of the current panel.
21254
- */
21255
- function repositionPanel(panel) {
21256
- var scrollY = DomUtils.getViewPort().y;
21257
-
21258
- function toggleFixedChildPanels(fixed, deltaY) {
21259
- var parent;
21260
-
21261
- for (var i = 0; i < visiblePanels.length; i++) {
21262
- if (visiblePanels[i] != panel) {
21263
- parent = visiblePanels[i].parent();
21264
-
21265
- while (parent && (parent = parent.parent())) {
21266
- if (parent == panel) {
21267
- visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
21268
- }
21269
- }
21270
- }
21271
- }
21272
- }
21273
-
21274
- if (panel.settings.autofix) {
21275
- if (!panel._fixed) {
21276
- panel._autoFixY = panel.layoutRect().y;
21277
-
21278
- if (panel._autoFixY < scrollY) {
21279
- panel.fixed(true).layoutRect({y: 0}).repaint();
21280
- toggleFixedChildPanels(true, scrollY - panel._autoFixY);
21281
- }
21282
- } else {
21283
- if (panel._autoFixY > scrollY) {
21284
- panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
21285
- toggleFixedChildPanels(false, panel._autoFixY - scrollY);
21286
- }
21287
- }
21288
- }
21289
- }
21290
-
21291
21686
  self._super(settings);
21292
21687
  self._eventsRoot = self;
21293
21688
 
@@ -21295,48 +21690,13 @@ define("tinymce/ui/FloatPanel", [
21295
21690
 
21296
21691
  // Hide floatpanes on click out side the root button
21297
21692
  if (settings.autohide) {
21298
- if (!documentClickHandler) {
21299
- documentClickHandler = function(e) {
21300
- // Hide any float panel when a click is out side that float panel and the
21301
- // float panels direct parent for example a click on a menu button
21302
- var i = visiblePanels.length;
21303
- while (i--) {
21304
- var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
21305
-
21306
- if (panel.settings.autohide) {
21307
- if (clickCtrl) {
21308
- if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
21309
- continue;
21310
- }
21311
- }
21312
-
21313
- e = panel.fire('autohide', {target: e.target});
21314
- if (!e.isDefaultPrevented()) {
21315
- panel.hide();
21316
- }
21317
- }
21318
- }
21319
- };
21320
-
21321
- DomUtils.on(document, 'click', documentClickHandler);
21322
- }
21323
-
21693
+ bindDocumentClickHandler();
21694
+ bindWindowResizeHandler();
21324
21695
  visiblePanels.push(self);
21325
21696
  }
21326
21697
 
21327
21698
  if (settings.autofix) {
21328
- if (!documentScrollHandler) {
21329
- documentScrollHandler = function() {
21330
- var i;
21331
-
21332
- i = visiblePanels.length;
21333
- while (i--) {
21334
- repositionPanel(visiblePanels[i]);
21335
- }
21336
- };
21337
-
21338
- DomUtils.on(window, 'scroll', documentScrollHandler);
21339
- }
21699
+ bindDocumentScrollHandler();
21340
21700
 
21341
21701
  self.on('move', function() {
21342
21702
  repositionPanel(this);
@@ -21452,7 +21812,8 @@ define("tinymce/ui/FloatPanel", [
21452
21812
  },
21453
21813
 
21454
21814
  /**
21455
- * Hides all visible the float panels.
21815
+ * Hide all visible float panels with he autohide setting enabled. This is for
21816
+ * manually hiding floating menus or panels.
21456
21817
  *
21457
21818
  * @method hideAll
21458
21819
  */
@@ -21495,7 +21856,8 @@ define("tinymce/ui/FloatPanel", [
21495
21856
  });
21496
21857
 
21497
21858
  /**
21498
- * Hides all visible the float panels.
21859
+ * Hide all visible float panels with he autohide setting enabled. This is for
21860
+ * manually hiding floating menus or panels.
21499
21861
  *
21500
21862
  * @static
21501
21863
  * @method hideAll
@@ -23901,8 +24263,13 @@ define("tinymce/Shortcuts", [
23901
24263
  break;
23902
24264
 
23903
24265
  default:
23904
- shortcut.charCode = value.charCodeAt(0);
23905
- shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
24266
+ // Allow numeric keycodes like ctrl+219 for ctrl+[
24267
+ if (/^[0-9]{2,}$/.test(value)) {
24268
+ shortcut.keyCode = parseInt(value, 10);
24269
+ } else {
24270
+ shortcut.charCode = value.charCodeAt(0);
24271
+ shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
24272
+ }
23906
24273
  }
23907
24274
  });
23908
24275
 
@@ -24709,7 +25076,14 @@ define("tinymce/Editor", [
24709
25076
  // Add internal attribute if we need to we don't on a refresh of the document
24710
25077
  if (!node.attributes.map[internalName]) {
24711
25078
  if (name === "style") {
24712
- node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
25079
+ value = dom.serializeStyle(dom.parseStyle(value), node.name);
25080
+
25081
+ if (!value.length) {
25082
+ value = null;
25083
+ }
25084
+
25085
+ node.attr(internalName, value);
25086
+ node.attr(name, value);
24713
25087
  } else if (name === "tabindex") {
24714
25088
  node.attr(internalName, value);
24715
25089
  node.attr(name, null);
@@ -24726,7 +25100,7 @@ define("tinymce/Editor", [
24726
25100
 
24727
25101
  while (i--) {
24728
25102
  node = nodes[i];
24729
- node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
25103
+ node.attr('type', 'mce-' + (node.attr('type') || 'no/type'));
24730
25104
  }
24731
25105
  });
24732
25106
 
@@ -25340,8 +25714,18 @@ define("tinymce/Editor", [
25340
25714
  }
25341
25715
 
25342
25716
  // Browser commands
25343
- self.getDoc().execCommand(cmd, ui, value);
25344
- self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
25717
+ try {
25718
+ state = self.getDoc().execCommand(cmd, ui, value);
25719
+ } catch (ex) {
25720
+ // Ignore old IE errors
25721
+ }
25722
+
25723
+ if (state) {
25724
+ self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
25725
+ return true;
25726
+ }
25727
+
25728
+ return false;
25345
25729
  },
25346
25730
 
25347
25731
  /**
@@ -25363,8 +25747,8 @@ define("tinymce/Editor", [
25363
25747
  if ((queryItem = self.queryStateCommands[cmd])) {
25364
25748
  returnVal = queryItem.func.call(queryItem.scope);
25365
25749
 
25366
- // Fall though on true
25367
- if (returnVal !== true) {
25750
+ // Fall though on non boolean returns
25751
+ if (returnVal === true || returnVal === false) {
25368
25752
  return returnVal;
25369
25753
  }
25370
25754
  }
@@ -25454,8 +25838,6 @@ define("tinymce/Editor", [
25454
25838
  var self = this, doc = self.getDoc();
25455
25839
 
25456
25840
  if (!self.hidden) {
25457
- self.hidden = true;
25458
-
25459
25841
  // Fixed bug where IE has a blinking cursor left from the editor
25460
25842
  if (ie && doc && !self.inline) {
25461
25843
  doc.execCommand('SelectAll');
@@ -25476,6 +25858,7 @@ define("tinymce/Editor", [
25476
25858
  DOM.setStyle(self.id, 'display', self.orgDisplay);
25477
25859
  }
25478
25860
 
25861
+ self.hidden = true;
25479
25862
  self.fire('hide');
25480
25863
  }
25481
25864
  },
@@ -25739,8 +26122,13 @@ define("tinymce/Editor", [
25739
26122
  *
25740
26123
  * @method insertContent
25741
26124
  * @param {String} content Content to insert.
26125
+ * @param {Object} args Optional args to pass to insert call.
25742
26126
  */
25743
- insertContent: function(content) {
26127
+ insertContent: function(content, args) {
26128
+ if (args) {
26129
+ content = extend({content: content}, args);
26130
+ }
26131
+
25744
26132
  this.execCommand('mceInsertContent', false, content);
25745
26133
  },
25746
26134
 
@@ -26495,7 +26883,7 @@ define("tinymce/EditorManager", [
26495
26883
  * @property minorVersion
26496
26884
  * @type String
26497
26885
  */
26498
- minorVersion : '0.26',
26886
+ minorVersion : '0.28',
26499
26887
 
26500
26888
  /**
26501
26889
  * Release date of TinyMCE build.
@@ -26503,7 +26891,7 @@ define("tinymce/EditorManager", [
26503
26891
  * @property releaseDate
26504
26892
  * @type String
26505
26893
  */
26506
- releaseDate: '2014-05-06',
26894
+ releaseDate: '2014-05-27',
26507
26895
 
26508
26896
  /**
26509
26897
  * Collection of editor instances.
@@ -26539,9 +26927,16 @@ define("tinymce/EditorManager", [
26539
26927
  var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
26540
26928
 
26541
26929
  // Get base URL for the current document
26542
- documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
26543
- if (!/[\/\\]$/.test(documentBaseURL)) {
26544
- documentBaseURL += '/';
26930
+ documentBaseURL = document.location.href;
26931
+
26932
+ // Check if the URL is a document based format like: http://site/dir/file
26933
+ // leave other formats like applewebdata://... intact
26934
+ if (/^[^:]+:\/\/[^\/]+\//.test(documentBaseURL)) {
26935
+ documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
26936
+
26937
+ if (!/[\/\\]$/.test(documentBaseURL)) {
26938
+ documentBaseURL += '/';
26939
+ }
26545
26940
  }
26546
26941
 
26547
26942
  // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
@@ -29475,13 +29870,19 @@ define("tinymce/ui/Form", [
29475
29870
  * @private
29476
29871
  */
29477
29872
  recalcLabels: function() {
29478
- var self = this, maxLabelWidth = 0, labels = [], i, labelGap;
29873
+ var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items;
29479
29874
 
29480
29875
  if (self.settings.labelGapCalc === false) {
29481
29876
  return;
29482
29877
  }
29483
29878
 
29484
- self.items().filter('formitem').each(function(item) {
29879
+ if (self.settings.labelGapCalc == "children") {
29880
+ items = self.find('formitem');
29881
+ } else {
29882
+ items = self.items();
29883
+ }
29884
+
29885
+ items.filter('formitem').each(function(item) {
29485
29886
  var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
29486
29887
 
29487
29888
  maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
@@ -29622,8 +30023,9 @@ define("tinymce/ui/FieldSet", [
29622
30023
  * @extends tinymce.ui.ComboBox
29623
30024
  */
29624
30025
  define("tinymce/ui/FilePicker", [
29625
- "tinymce/ui/ComboBox"
29626
- ], function(ComboBox) {
30026
+ "tinymce/ui/ComboBox",
30027
+ "tinymce/util/Tools"
30028
+ ], function(ComboBox, Tools) {
29627
30029
  "use strict";
29628
30030
 
29629
30031
  return ComboBox.extend({
@@ -29634,12 +30036,17 @@ define("tinymce/ui/FilePicker", [
29634
30036
  * @param {Object} settings Name/value object with settings.
29635
30037
  */
29636
30038
  init: function(settings) {
29637
- var self = this, editor = tinymce.activeEditor, fileBrowserCallback;
30039
+ var self = this, editor = tinymce.activeEditor, fileBrowserCallback, fileBrowserCallbackTypes;
29638
30040
 
29639
30041
  settings.spellcheck = false;
29640
30042
 
30043
+ fileBrowserCallbackTypes = editor.settings.file_browser_callback_types;
30044
+ if (fileBrowserCallbackTypes) {
30045
+ fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
30046
+ }
30047
+
29641
30048
  fileBrowserCallback = editor.settings.file_browser_callback;
29642
- if (fileBrowserCallback) {
30049
+ if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
29643
30050
  settings.icon = 'browse';
29644
30051
 
29645
30052
  settings.onaction = function() {
@@ -30155,6 +30562,7 @@ define("tinymce/ui/FormatControls", [
30155
30562
  }
30156
30563
 
30157
30564
  menuItem.format = formatName;
30565
+ menuItem.cmd = format.cmd;
30158
30566
  }
30159
30567
 
30160
30568
  menu.push(menuItem);
@@ -30201,20 +30609,32 @@ define("tinymce/ui/FormatControls", [
30201
30609
  },
30202
30610
 
30203
30611
  onPostRender: function() {
30204
- var self = this, formatName = this.settings.format;
30612
+ var self = this;
30205
30613
 
30206
- if (formatName) {
30207
- self.parent().on('show', function() {
30614
+ self.parent().on('show', function() {
30615
+ var formatName, command;
30616
+
30617
+ formatName = self.settings.format;
30618
+ if (formatName) {
30208
30619
  self.disabled(!editor.formatter.canApply(formatName));
30209
30620
  self.active(editor.formatter.match(formatName));
30210
- });
30211
- }
30621
+ }
30622
+
30623
+ command = self.settings.cmd;
30624
+ if (command) {
30625
+ self.active(editor.queryCommandState(command));
30626
+ }
30627
+ });
30212
30628
  },
30213
30629
 
30214
30630
  onclick: function() {
30215
30631
  if (this.settings.format) {
30216
30632
  toggleFormat(this.settings.format);
30217
30633
  }
30634
+
30635
+ if (this.settings.cmd) {
30636
+ editor.execCommand(this.settings.cmd);
30637
+ }
30218
30638
  }
30219
30639
  }
30220
30640
  };
@@ -30309,30 +30729,21 @@ define("tinymce/ui/FormatControls", [
30309
30729
  });
30310
30730
  });
30311
30731
 
30312
- function hasUndo() {
30313
- return editor.undoManager ? editor.undoManager.hasUndo() : false;
30314
- }
30315
-
30316
- function hasRedo() {
30317
- return editor.undoManager ? editor.undoManager.hasRedo() : false;
30318
- }
30319
-
30320
- function toggleUndoState() {
30321
- var self = this;
30732
+ function toggleUndoRedoState(type) {
30733
+ return function() {
30734
+ var self = this;
30322
30735
 
30323
- self.disabled(!hasUndo());
30324
- editor.on('Undo Redo AddUndo TypingUndo', function() {
30325
- self.disabled(!hasUndo());
30326
- });
30327
- }
30736
+ type = type == 'redo' ? 'hasRedo' : 'hasUndo';
30328
30737
 
30329
- function toggleRedoState() {
30330
- var self = this;
30738
+ function checkState() {
30739
+ return editor.undoManager ? editor.undoManager[type]() : false;
30740
+ }
30331
30741
 
30332
- self.disabled(!hasRedo());
30333
- editor.on('Undo Redo AddUndo TypingUndo', function() {
30334
- self.disabled(!hasRedo());
30335
- });
30742
+ self.disabled(!checkState());
30743
+ editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() {
30744
+ self.disabled(!checkState());
30745
+ });
30746
+ };
30336
30747
  }
30337
30748
 
30338
30749
  function toggleVisualAidState() {
@@ -30347,13 +30758,13 @@ define("tinymce/ui/FormatControls", [
30347
30758
 
30348
30759
  editor.addButton('undo', {
30349
30760
  tooltip: 'Undo',
30350
- onPostRender: toggleUndoState,
30761
+ onPostRender: toggleUndoRedoState('undo'),
30351
30762
  cmd: 'undo'
30352
30763
  });
30353
30764
 
30354
30765
  editor.addButton('redo', {
30355
30766
  tooltip: 'Redo',
30356
- onPostRender: toggleRedoState,
30767
+ onPostRender: toggleUndoRedoState('redo'),
30357
30768
  cmd: 'redo'
30358
30769
  });
30359
30770
 
@@ -30368,7 +30779,7 @@ define("tinymce/ui/FormatControls", [
30368
30779
  text: 'Undo',
30369
30780
  icon: 'undo',
30370
30781
  shortcut: 'Ctrl+Z',
30371
- onPostRender: toggleUndoState,
30782
+ onPostRender: toggleUndoRedoState('undo'),
30372
30783
  cmd: 'undo'
30373
30784
  });
30374
30785
 
@@ -30376,7 +30787,7 @@ define("tinymce/ui/FormatControls", [
30376
30787
  text: 'Redo',
30377
30788
  icon: 'redo',
30378
30789
  shortcut: 'Ctrl+Y',
30379
- onPostRender: toggleRedoState,
30790
+ onPostRender: toggleUndoRedoState('redo'),
30380
30791
  cmd: 'redo'
30381
30792
  });
30382
30793
 
@@ -30511,7 +30922,14 @@ define("tinymce/ui/FormatControls", [
30511
30922
  var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
30512
30923
 
30513
30924
  each(fontsize_formats.split(' '), function(item) {
30514
- items.push({text: item, value: item});
30925
+ var text = item, value = item;
30926
+ // Allow text=value font sizes.
30927
+ var values = item.split('=');
30928
+ if (values.length > 1) {
30929
+ text = values[0];
30930
+ value = values[1];
30931
+ }
30932
+ items.push({text: text, value: value});
30515
30933
  });
30516
30934
 
30517
30935
  return {
@@ -31353,19 +31771,29 @@ define("tinymce/ui/ListBox", [
31353
31771
  * @setting {Array} values Array with values to add to list box.
31354
31772
  */
31355
31773
  init: function(settings) {
31356
- var self = this, values, i, selected, selectedText, lastItemCtrl;
31774
+ var self = this, values, selected, selectedText, lastItemCtrl;
31357
31775
 
31358
- self._values = values = settings.values;
31359
- if (values) {
31360
- for (i = 0; i < values.length; i++) {
31361
- selected = values[i].selected || settings.value === values[i].value;
31776
+ function setSelected(menuValues) {
31777
+ // Try to find a selected value
31778
+ for (var i = 0; i < menuValues.length; i++) {
31779
+ selected = menuValues[i].selected || settings.value === menuValues[i].value;
31362
31780
 
31363
31781
  if (selected) {
31364
- selectedText = selectedText || values[i].text;
31365
- self._value = values[i].value;
31782
+ selectedText = selectedText || menuValues[i].text;
31783
+ self._value = menuValues[i].value;
31366
31784
  break;
31367
31785
  }
31786
+
31787
+ // If the value has a submenu, try to find the selected values in that menu
31788
+ if (menuValues[i].menu) {
31789
+ setSelected(menuValues[i].menu);
31790
+ }
31368
31791
  }
31792
+ }
31793
+
31794
+ self._values = values = settings.values;
31795
+ if (values) {
31796
+ setSelected(values);
31369
31797
 
31370
31798
  // Default with first item
31371
31799
  if (!selected && values.length > 0) {
@@ -31406,7 +31834,7 @@ define("tinymce/ui/ListBox", [
31406
31834
  * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
31407
31835
  */
31408
31836
  value: function(value) {
31409
- var self = this, active, selectedText, menu, i;
31837
+ var self = this, active, selectedText, menu;
31410
31838
 
31411
31839
  function activateByValue(menu, value) {
31412
31840
  menu.items().each(function(ctrl) {
@@ -31424,20 +31852,28 @@ define("tinymce/ui/ListBox", [
31424
31852
  });
31425
31853
  }
31426
31854
 
31855
+ function setActiveValues(menuValues) {
31856
+ for (var i = 0; i < menuValues.length; i++) {
31857
+ active = menuValues[i].value == value;
31858
+
31859
+ if (active) {
31860
+ selectedText = selectedText || menuValues[i].text;
31861
+ }
31862
+
31863
+ menuValues[i].active = active;
31864
+
31865
+ if (menuValues[i].menu) {
31866
+ setActiveValues(menuValues[i].menu);
31867
+ }
31868
+ }
31869
+ }
31870
+
31427
31871
  if (typeof(value) != "undefined") {
31428
31872
  if (self.menu) {
31429
31873
  activateByValue(self.menu, value);
31430
31874
  } else {
31431
31875
  menu = self.settings.menu;
31432
- for (i = 0; i < menu.length; i++) {
31433
- active = menu[i].value == value;
31434
-
31435
- if (active) {
31436
- selectedText = selectedText || menu[i].text;
31437
- }
31438
-
31439
- menu[i].active = active;
31440
- }
31876
+ setActiveValues(menu);
31441
31877
  }
31442
31878
 
31443
31879
  self.text(selectedText || this.settings.text);
@@ -31579,12 +32015,16 @@ define("tinymce/ui/MenuItem", [
31579
32015
 
31580
32016
  menu = self.menu = Factory.create(menu).parent(self).renderTo();
31581
32017
  menu.reflow();
31582
- menu.fire('show');
31583
32018
  menu.on('cancel', function(e) {
31584
32019
  e.stopPropagation();
31585
32020
  self.focus();
31586
32021
  menu.hide();
31587
32022
  });
32023
+ menu.on('show hide', function(e) {
32024
+ e.control.items().each(function(ctrl) {
32025
+ ctrl.active(ctrl.settings.selected);
32026
+ });
32027
+ }).fire('show');
31588
32028
 
31589
32029
  menu.on('hide', function(e) {
31590
32030
  if (e.control === menu) {
@@ -32681,5 +33121,5 @@ define("tinymce/ui/Throbber", [
32681
33121
  };
32682
33122
  });
32683
33123
 
32684
- expose(["tinymce/dom/Sizzle","tinymce/html/Styles","tinymce/dom/EventUtils","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"]);
33124
+ expose(["tinymce/dom/Sizzle","tinymce/html/Styles","tinymce/dom/EventUtils","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"]);
32685
33125
  })(this);