tinymce-rails 4.0.16 → 4.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/source/tinymce/tinymce.jquery.js +3060 -2879
  3. data/app/assets/source/tinymce/tinymce.js +1386 -1205
  4. data/lib/tinymce/rails/configuration.rb +15 -1
  5. data/lib/tinymce/rails/helper.rb +4 -4
  6. data/lib/tinymce/rails/version.rb +2 -2
  7. data/vendor/assets/javascripts/tinymce/plugins/autolink/plugin.js +1 -1
  8. data/vendor/assets/javascripts/tinymce/plugins/autoresize/plugin.js +1 -1
  9. data/vendor/assets/javascripts/tinymce/plugins/charmap/plugin.js +1 -1
  10. data/vendor/assets/javascripts/tinymce/plugins/contextmenu/plugin.js +1 -1
  11. data/vendor/assets/javascripts/tinymce/plugins/emoticons/plugin.js +1 -1
  12. data/vendor/assets/javascripts/tinymce/plugins/fullpage/plugin.js +1 -1
  13. data/vendor/assets/javascripts/tinymce/plugins/image/plugin.js +1 -1
  14. data/vendor/assets/javascripts/tinymce/plugins/insertdatetime/plugin.js +1 -1
  15. data/vendor/assets/javascripts/tinymce/plugins/link/plugin.js +1 -1
  16. data/vendor/assets/javascripts/tinymce/plugins/media/plugin.js +1 -1
  17. data/vendor/assets/javascripts/tinymce/plugins/nonbreaking/plugin.js +1 -1
  18. data/vendor/assets/javascripts/tinymce/plugins/paste/plugin.js +1 -1
  19. data/vendor/assets/javascripts/tinymce/plugins/searchreplace/plugin.js +1 -1
  20. data/vendor/assets/javascripts/tinymce/plugins/spellchecker/plugin.js +1 -1
  21. data/vendor/assets/javascripts/tinymce/plugins/table/plugin.js +1 -1
  22. data/vendor/assets/javascripts/tinymce/plugins/textcolor/plugin.js +1 -1
  23. data/vendor/assets/javascripts/tinymce/plugins/visualchars/plugin.js +1 -1
  24. data/vendor/assets/javascripts/tinymce/plugins/wordcount/plugin.js +1 -1
  25. data/vendor/assets/javascripts/tinymce/skins/lightgray/content.inline.min.css +1 -1
  26. data/vendor/assets/javascripts/tinymce/skins/lightgray/content.min.css +1 -1
  27. data/vendor/assets/javascripts/tinymce/skins/lightgray/skin.ie7.min.css +1 -1
  28. data/vendor/assets/javascripts/tinymce/skins/lightgray/skin.min.css +1 -1
  29. data/vendor/assets/javascripts/tinymce/themes/modern/theme.js +1 -1
  30. data/vendor/assets/javascripts/tinymce/tinymce.jquery.js +10 -9
  31. data/vendor/assets/javascripts/tinymce/tinymce.js +10 -10
  32. metadata +13 -18
@@ -1,4 +1,4 @@
1
- // 4.0.16 (2014-01-31)
1
+ // 4.0.18 (2014-02-27)
2
2
 
3
3
  /**
4
4
  * Compiled inline version. (Library mode)
@@ -192,7 +192,7 @@ define("tinymce/dom/EventUtils", [], function() {
192
192
  } else {
193
193
  originalEvent.cancelBubble = true; // IE
194
194
  }
195
- }
195
+ }
196
196
  };
197
197
 
198
198
  // Add stopImmediatePropagation
@@ -232,7 +232,9 @@ define("tinymce/dom/EventUtils", [], function() {
232
232
  }
233
233
 
234
234
  function waitForDomLoaded() {
235
- if (doc.readyState === "complete" || doc.readyState === "interactive") {
235
+ // Check complete or interactive state if there is a body
236
+ // element on some iframes IE 8 will produce a null body
237
+ if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
236
238
  removeEvent(doc, "readystatechange", waitForDomLoaded);
237
239
  readyHandler();
238
240
  }
@@ -4604,7 +4606,7 @@ define("tinymce/dom/Range", [
4604
4606
  return _traverseCommonAncestors(startNode, endNode, how);
4605
4607
  }
4606
4608
 
4607
- function _traverseSameContainer(how) {
4609
+ function _traverseSameContainer(how) {
4608
4610
  var frag, s, sub, n, cnt, sibling, xferNode, start, len;
4609
4611
 
4610
4612
  if (how != DELETE) {
@@ -5392,190 +5394,190 @@ define("tinymce/Env", [], function() {
5392
5394
 
5393
5395
  // Included from: js/tinymce/classes/dom/StyleSheetLoader.js
5394
5396
 
5395
- /**
5396
- * StyleSheetLoader.js
5397
- *
5398
- * Copyright, Moxiecode Systems AB
5399
- * Released under LGPL License.
5400
- *
5401
- * License: http://www.tinymce.com/license
5402
- * Contributing: http://www.tinymce.com/contributing
5403
- */
5404
-
5405
- /**
5406
- * This class handles loading of external stylesheets and fires events when these are loaded.
5407
- *
5408
- * @class tinymce.dom.StyleSheetLoader
5409
- * @private
5410
- */
5411
- define("tinymce/dom/StyleSheetLoader", [], function() {
5412
- "use strict";
5413
-
5414
- return function(document, settings) {
5415
- var idCount = 0, loadedStates = {}, maxLoadTime;
5416
-
5417
- settings = settings || {};
5418
- maxLoadTime = settings.maxLoadTime || 5000;
5419
-
5420
- function appendToHead(node) {
5421
- document.getElementsByTagName('head')[0].appendChild(node);
5422
- }
5423
-
5424
- /**
5425
- * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
5426
- *
5427
- * @method load
5428
- * @param {String} url Url to be loaded.
5429
- * @param {Function} loadedCallback Callback to be executed when loaded.
5430
- * @param {Function} errorCallback Callback to be executed when failed loading.
5431
- */
5432
- function load(url, loadedCallback, errorCallback) {
5433
- var link, style, startTime, state;
5434
-
5435
- function passed() {
5436
- var callbacks = state.passed, i = callbacks.length;
5437
-
5438
- while (i--) {
5439
- callbacks[i]();
5440
- }
5441
-
5442
- state.status = 2;
5443
- state.passed = [];
5444
- state.failed = [];
5445
- }
5446
-
5447
- function failed() {
5448
- var callbacks = state.failed, i = callbacks.length;
5449
-
5450
- while (i--) {
5451
- callbacks[i]();
5452
- }
5453
-
5454
- state.status = 3;
5455
- state.passed = [];
5456
- state.failed = [];
5457
- }
5458
-
5459
- // Sniffs for older WebKit versions that have the link.onload but a broken one
5460
- function isOldWebKit() {
5461
- var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
5462
- return !!(webKitChunks && webKitChunks[1] < 536);
5463
- }
5464
-
5465
- // Calls the waitCallback until the test returns true or the timeout occurs
5466
- function wait(testCallback, waitCallback) {
5467
- if (!testCallback()) {
5468
- // Wait for timeout
5469
- if ((new Date().getTime()) - startTime < maxLoadTime) {
5470
- window.setTimeout(waitCallback, 0);
5471
- } else {
5472
- failed();
5473
- }
5474
- }
5475
- }
5476
-
5477
- // Workaround for WebKit that doesn't properly support the onload event for link elements
5478
- // Or WebKit that fires the onload event before the StyleSheet is added to the document
5479
- function waitForWebKitLinkLoaded() {
5480
- wait(function() {
5481
- var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
5482
-
5483
- while (i--) {
5484
- styleSheet = styleSheets[i];
5485
- owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
5486
- if (owner && owner.id === link.id) {
5487
- passed();
5488
- return true;
5489
- }
5490
- }
5491
- }, waitForWebKitLinkLoaded);
5492
- }
5493
-
5494
- // Workaround for older Geckos that doesn't have any onload event for StyleSheets
5495
- function waitForGeckoLinkLoaded() {
5496
- wait(function() {
5497
- try {
5498
- // Accessing the cssRules will throw an exception until the CSS file is loaded
5499
- var cssRules = style.sheet.cssRules;
5500
- passed();
5501
- return !!cssRules;
5502
- } catch (ex) {
5503
- // Ignore
5504
- }
5505
- }, waitForGeckoLinkLoaded);
5506
- }
5507
-
5508
- if (!loadedStates[url]) {
5509
- state = {
5510
- passed: [],
5511
- failed: []
5512
- };
5513
-
5514
- loadedStates[url] = state;
5515
- } else {
5516
- state = loadedStates[url];
5517
- }
5518
-
5519
- if (loadedCallback) {
5520
- state.passed.push(loadedCallback);
5521
- }
5522
-
5523
- if (errorCallback) {
5524
- state.failed.push(errorCallback);
5525
- }
5526
-
5527
- // Is loading wait for it to pass
5528
- if (state.status == 1) {
5529
- return;
5530
- }
5531
-
5532
- // Has finished loading and was success
5533
- if (state.status == 2) {
5534
- passed();
5535
- return;
5536
- }
5537
-
5538
- // Has finished loading and was a failure
5539
- if (state.status == 3) {
5540
- failed();
5541
- return;
5542
- }
5543
-
5544
- // Start loading
5545
- state.status = 1;
5546
- link = document.createElement('link');
5547
- link.rel = 'stylesheet';
5548
- link.type = 'text/css';
5549
- link.id = 'u' + (idCount++);
5550
- link.async = false;
5551
- link.defer = false;
5552
- startTime = new Date().getTime();
5553
-
5554
- // Feature detect onload on link element and sniff older webkits since it has an broken onload event
5555
- if ("onload" in link && !isOldWebKit()) {
5556
- link.onload = waitForWebKitLinkLoaded;
5557
- link.onerror = failed;
5558
- } else {
5559
- // Sniff for old Firefox that doesn't support the onload event on link elements
5560
- // TODO: Remove this in the future when everyone uses modern browsers
5561
- if (navigator.userAgent.indexOf("Firefox") > 0) {
5562
- style = document.createElement('style');
5563
- style.textContent = '@import "' + url + '"';
5564
- waitForGeckoLinkLoaded();
5565
- appendToHead(style);
5566
- return;
5567
- } else {
5568
- // Use the id owner on older webkits
5569
- waitForWebKitLinkLoaded();
5570
- }
5571
- }
5572
-
5573
- appendToHead(link);
5574
- link.href = url;
5575
- }
5576
-
5577
- this.load = load;
5578
- };
5397
+ /**
5398
+ * StyleSheetLoader.js
5399
+ *
5400
+ * Copyright, Moxiecode Systems AB
5401
+ * Released under LGPL License.
5402
+ *
5403
+ * License: http://www.tinymce.com/license
5404
+ * Contributing: http://www.tinymce.com/contributing
5405
+ */
5406
+
5407
+ /**
5408
+ * This class handles loading of external stylesheets and fires events when these are loaded.
5409
+ *
5410
+ * @class tinymce.dom.StyleSheetLoader
5411
+ * @private
5412
+ */
5413
+ define("tinymce/dom/StyleSheetLoader", [], function() {
5414
+ "use strict";
5415
+
5416
+ return function(document, settings) {
5417
+ var idCount = 0, loadedStates = {}, maxLoadTime;
5418
+
5419
+ settings = settings || {};
5420
+ maxLoadTime = settings.maxLoadTime || 5000;
5421
+
5422
+ function appendToHead(node) {
5423
+ document.getElementsByTagName('head')[0].appendChild(node);
5424
+ }
5425
+
5426
+ /**
5427
+ * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
5428
+ *
5429
+ * @method load
5430
+ * @param {String} url Url to be loaded.
5431
+ * @param {Function} loadedCallback Callback to be executed when loaded.
5432
+ * @param {Function} errorCallback Callback to be executed when failed loading.
5433
+ */
5434
+ function load(url, loadedCallback, errorCallback) {
5435
+ var link, style, startTime, state;
5436
+
5437
+ function passed() {
5438
+ var callbacks = state.passed, i = callbacks.length;
5439
+
5440
+ while (i--) {
5441
+ callbacks[i]();
5442
+ }
5443
+
5444
+ state.status = 2;
5445
+ state.passed = [];
5446
+ state.failed = [];
5447
+ }
5448
+
5449
+ function failed() {
5450
+ var callbacks = state.failed, i = callbacks.length;
5451
+
5452
+ while (i--) {
5453
+ callbacks[i]();
5454
+ }
5455
+
5456
+ state.status = 3;
5457
+ state.passed = [];
5458
+ state.failed = [];
5459
+ }
5460
+
5461
+ // Sniffs for older WebKit versions that have the link.onload but a broken one
5462
+ function isOldWebKit() {
5463
+ var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
5464
+ return !!(webKitChunks && webKitChunks[1] < 536);
5465
+ }
5466
+
5467
+ // Calls the waitCallback until the test returns true or the timeout occurs
5468
+ function wait(testCallback, waitCallback) {
5469
+ if (!testCallback()) {
5470
+ // Wait for timeout
5471
+ if ((new Date().getTime()) - startTime < maxLoadTime) {
5472
+ window.setTimeout(waitCallback, 0);
5473
+ } else {
5474
+ failed();
5475
+ }
5476
+ }
5477
+ }
5478
+
5479
+ // Workaround for WebKit that doesn't properly support the onload event for link elements
5480
+ // Or WebKit that fires the onload event before the StyleSheet is added to the document
5481
+ function waitForWebKitLinkLoaded() {
5482
+ wait(function() {
5483
+ var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
5484
+
5485
+ while (i--) {
5486
+ styleSheet = styleSheets[i];
5487
+ owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
5488
+ if (owner && owner.id === link.id) {
5489
+ passed();
5490
+ return true;
5491
+ }
5492
+ }
5493
+ }, waitForWebKitLinkLoaded);
5494
+ }
5495
+
5496
+ // Workaround for older Geckos that doesn't have any onload event for StyleSheets
5497
+ function waitForGeckoLinkLoaded() {
5498
+ wait(function() {
5499
+ try {
5500
+ // Accessing the cssRules will throw an exception until the CSS file is loaded
5501
+ var cssRules = style.sheet.cssRules;
5502
+ passed();
5503
+ return !!cssRules;
5504
+ } catch (ex) {
5505
+ // Ignore
5506
+ }
5507
+ }, waitForGeckoLinkLoaded);
5508
+ }
5509
+
5510
+ if (!loadedStates[url]) {
5511
+ state = {
5512
+ passed: [],
5513
+ failed: []
5514
+ };
5515
+
5516
+ loadedStates[url] = state;
5517
+ } else {
5518
+ state = loadedStates[url];
5519
+ }
5520
+
5521
+ if (loadedCallback) {
5522
+ state.passed.push(loadedCallback);
5523
+ }
5524
+
5525
+ if (errorCallback) {
5526
+ state.failed.push(errorCallback);
5527
+ }
5528
+
5529
+ // Is loading wait for it to pass
5530
+ if (state.status == 1) {
5531
+ return;
5532
+ }
5533
+
5534
+ // Has finished loading and was success
5535
+ if (state.status == 2) {
5536
+ passed();
5537
+ return;
5538
+ }
5539
+
5540
+ // Has finished loading and was a failure
5541
+ if (state.status == 3) {
5542
+ failed();
5543
+ return;
5544
+ }
5545
+
5546
+ // Start loading
5547
+ state.status = 1;
5548
+ link = document.createElement('link');
5549
+ link.rel = 'stylesheet';
5550
+ link.type = 'text/css';
5551
+ link.id = 'u' + (idCount++);
5552
+ link.async = false;
5553
+ link.defer = false;
5554
+ startTime = new Date().getTime();
5555
+
5556
+ // Feature detect onload on link element and sniff older webkits since it has an broken onload event
5557
+ if ("onload" in link && !isOldWebKit()) {
5558
+ link.onload = waitForWebKitLinkLoaded;
5559
+ link.onerror = failed;
5560
+ } else {
5561
+ // Sniff for old Firefox that doesn't support the onload event on link elements
5562
+ // TODO: Remove this in the future when everyone uses modern browsers
5563
+ if (navigator.userAgent.indexOf("Firefox") > 0) {
5564
+ style = document.createElement('style');
5565
+ style.textContent = '@import "' + url + '"';
5566
+ waitForGeckoLinkLoaded();
5567
+ appendToHead(style);
5568
+ return;
5569
+ } else {
5570
+ // Use the id owner on older webkits
5571
+ waitForWebKitLinkLoaded();
5572
+ }
5573
+ }
5574
+
5575
+ appendToHead(link);
5576
+ link.href = url;
5577
+ }
5578
+
5579
+ this.load = load;
5580
+ };
5579
5581
  });
5580
5582
 
5581
5583
  // Included from: js/tinymce/classes/dom/DOMUtils.js
@@ -9052,7 +9054,7 @@ define("tinymce/html/Schema", [
9052
9054
  textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
9053
9055
  'blockquote center dir fieldset header footer article section hgroup aside nav figure');
9054
9056
  blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
9055
- 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option ' +
9057
+ 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
9056
9058
  'datalist select optgroup', textBlockElementsMap);
9057
9059
 
9058
9060
  each((settings.special || 'script noscript style textarea').split(' '), function(name) {
@@ -9566,7 +9568,7 @@ define("tinymce/html/Schema", [
9566
9568
 
9567
9569
  /**
9568
9570
  * Parses a valid elements string and adds it to the schema. The valid elements
9569
- format is for example "element[attr=default|otherattr]".
9571
+ * format is for example "element[attr=default|otherattr]".
9570
9572
  * Existing rules will be replaced with the ones specified, so this extends the schema.
9571
9573
  *
9572
9574
  * @method addValidElements
@@ -12273,8 +12275,6 @@ define("tinymce/dom/ControlSelection", [
12273
12275
  var handleElm, handlerContainerElm;
12274
12276
 
12275
12277
  function startDrag(e) {
12276
- resizeStarted = true;
12277
-
12278
12278
  startX = e.screenX;
12279
12279
  startY = e.screenY;
12280
12280
  startW = selectedElm.clientWidth;
@@ -12322,12 +12322,18 @@ define("tinymce/dom/ControlSelection", [
12322
12322
  id: 'mceResizeHandle' + name,
12323
12323
  'data-mce-bogus': true,
12324
12324
  'class': 'mce-resizehandle',
12325
- contentEditable: false, // Hides IE move layer cursor
12326
- unSelectabe: true,
12325
+ unselectable: true,
12327
12326
  style: 'cursor:' + name + '-resize; margin:0; padding:0'
12328
12327
  });
12329
12328
 
12329
+ // Hides IE move layer cursor
12330
+ // If we set it on Chrome we get this wounderful bug: #6725
12331
+ if (Env.ie) {
12332
+ handleElm.contentEditable = false;
12333
+ }
12334
+
12330
12335
  dom.bind(handleElm, 'mousedown', function(e) {
12336
+ e.stopImmediatePropagation();
12331
12337
  e.preventDefault();
12332
12338
  startDrag(e);
12333
12339
  });
@@ -12568,6 +12574,485 @@ define("tinymce/dom/ControlSelection", [
12568
12574
  };
12569
12575
  });
12570
12576
 
12577
+ // Included from: js/tinymce/classes/dom/RangeUtils.js
12578
+
12579
+ /**
12580
+ * Range.js
12581
+ *
12582
+ * Copyright, Moxiecode Systems AB
12583
+ * Released under LGPL License.
12584
+ *
12585
+ * License: http://www.tinymce.com/license
12586
+ * Contributing: http://www.tinymce.com/contributing
12587
+ */
12588
+
12589
+ /**
12590
+ * RangeUtils
12591
+ *
12592
+ * @class tinymce.dom.RangeUtils
12593
+ * @private
12594
+ */
12595
+ define("tinymce/dom/RangeUtils", [
12596
+ "tinymce/util/Tools",
12597
+ "tinymce/dom/TreeWalker"
12598
+ ], function(Tools, TreeWalker) {
12599
+ var each = Tools.each;
12600
+
12601
+ function RangeUtils(dom) {
12602
+ /**
12603
+ * Walks the specified range like object and executes the callback for each sibling collection it finds.
12604
+ *
12605
+ * @method walk
12606
+ * @param {Object} rng Range like object.
12607
+ * @param {function} callback Callback function to execute for each sibling collection.
12608
+ */
12609
+ this.walk = function(rng, callback) {
12610
+ var startContainer = rng.startContainer,
12611
+ startOffset = rng.startOffset,
12612
+ endContainer = rng.endContainer,
12613
+ endOffset = rng.endOffset,
12614
+ ancestor, startPoint,
12615
+ endPoint, node, parent, siblings, nodes;
12616
+
12617
+ // Handle table cell selection the table plugin enables
12618
+ // you to fake select table cells and perform formatting actions on them
12619
+ nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
12620
+ if (nodes.length > 0) {
12621
+ each(nodes, function(node) {
12622
+ callback([node]);
12623
+ });
12624
+
12625
+ return;
12626
+ }
12627
+
12628
+ /**
12629
+ * Excludes start/end text node if they are out side the range
12630
+ *
12631
+ * @private
12632
+ * @param {Array} nodes Nodes to exclude items from.
12633
+ * @return {Array} Array with nodes excluding the start/end container if needed.
12634
+ */
12635
+ function exclude(nodes) {
12636
+ var node;
12637
+
12638
+ // First node is excluded
12639
+ node = nodes[0];
12640
+ if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
12641
+ nodes.splice(0, 1);
12642
+ }
12643
+
12644
+ // Last node is excluded
12645
+ node = nodes[nodes.length - 1];
12646
+ if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
12647
+ nodes.splice(nodes.length - 1, 1);
12648
+ }
12649
+
12650
+ return nodes;
12651
+ }
12652
+
12653
+ /**
12654
+ * Collects siblings
12655
+ *
12656
+ * @private
12657
+ * @param {Node} node Node to collect siblings from.
12658
+ * @param {String} name Name of the sibling to check for.
12659
+ * @return {Array} Array of collected siblings.
12660
+ */
12661
+ function collectSiblings(node, name, end_node) {
12662
+ var siblings = [];
12663
+
12664
+ for (; node && node != end_node; node = node[name]) {
12665
+ siblings.push(node);
12666
+ }
12667
+
12668
+ return siblings;
12669
+ }
12670
+
12671
+ /**
12672
+ * Find an end point this is the node just before the common ancestor root.
12673
+ *
12674
+ * @private
12675
+ * @param {Node} node Node to start at.
12676
+ * @param {Node} root Root/ancestor element to stop just before.
12677
+ * @return {Node} Node just before the root element.
12678
+ */
12679
+ function findEndPoint(node, root) {
12680
+ do {
12681
+ if (node.parentNode == root) {
12682
+ return node;
12683
+ }
12684
+
12685
+ node = node.parentNode;
12686
+ } while(node);
12687
+ }
12688
+
12689
+ function walkBoundary(start_node, end_node, next) {
12690
+ var siblingName = next ? 'nextSibling' : 'previousSibling';
12691
+
12692
+ for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
12693
+ parent = node.parentNode;
12694
+ siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
12695
+
12696
+ if (siblings.length) {
12697
+ if (!next) {
12698
+ siblings.reverse();
12699
+ }
12700
+
12701
+ callback(exclude(siblings));
12702
+ }
12703
+ }
12704
+ }
12705
+
12706
+ // If index based start position then resolve it
12707
+ if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
12708
+ startContainer = startContainer.childNodes[startOffset];
12709
+ }
12710
+
12711
+ // If index based end position then resolve it
12712
+ if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
12713
+ endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
12714
+ }
12715
+
12716
+ // Same container
12717
+ if (startContainer == endContainer) {
12718
+ return callback(exclude([startContainer]));
12719
+ }
12720
+
12721
+ // Find common ancestor and end points
12722
+ ancestor = dom.findCommonAncestor(startContainer, endContainer);
12723
+
12724
+ // Process left side
12725
+ for (node = startContainer; node; node = node.parentNode) {
12726
+ if (node === endContainer) {
12727
+ return walkBoundary(startContainer, ancestor, true);
12728
+ }
12729
+
12730
+ if (node === ancestor) {
12731
+ break;
12732
+ }
12733
+ }
12734
+
12735
+ // Process right side
12736
+ for (node = endContainer; node; node = node.parentNode) {
12737
+ if (node === startContainer) {
12738
+ return walkBoundary(endContainer, ancestor);
12739
+ }
12740
+
12741
+ if (node === ancestor) {
12742
+ break;
12743
+ }
12744
+ }
12745
+
12746
+ // Find start/end point
12747
+ startPoint = findEndPoint(startContainer, ancestor) || startContainer;
12748
+ endPoint = findEndPoint(endContainer, ancestor) || endContainer;
12749
+
12750
+ // Walk left leaf
12751
+ walkBoundary(startContainer, startPoint, true);
12752
+
12753
+ // Walk the middle from start to end point
12754
+ siblings = collectSiblings(
12755
+ startPoint == startContainer ? startPoint : startPoint.nextSibling,
12756
+ 'nextSibling',
12757
+ endPoint == endContainer ? endPoint.nextSibling : endPoint
12758
+ );
12759
+
12760
+ if (siblings.length) {
12761
+ callback(exclude(siblings));
12762
+ }
12763
+
12764
+ // Walk right leaf
12765
+ walkBoundary(endContainer, endPoint);
12766
+ };
12767
+
12768
+ /**
12769
+ * Splits the specified range at it's start/end points.
12770
+ *
12771
+ * @private
12772
+ * @param {Range/RangeObject} rng Range to split.
12773
+ * @return {Object} Range position object.
12774
+ */
12775
+ this.split = function(rng) {
12776
+ var startContainer = rng.startContainer,
12777
+ startOffset = rng.startOffset,
12778
+ endContainer = rng.endContainer,
12779
+ endOffset = rng.endOffset;
12780
+
12781
+ function splitText(node, offset) {
12782
+ return node.splitText(offset);
12783
+ }
12784
+
12785
+ // Handle single text node
12786
+ if (startContainer == endContainer && startContainer.nodeType == 3) {
12787
+ if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
12788
+ endContainer = splitText(startContainer, startOffset);
12789
+ startContainer = endContainer.previousSibling;
12790
+
12791
+ if (endOffset > startOffset) {
12792
+ endOffset = endOffset - startOffset;
12793
+ startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
12794
+ endOffset = endContainer.nodeValue.length;
12795
+ startOffset = 0;
12796
+ } else {
12797
+ endOffset = 0;
12798
+ }
12799
+ }
12800
+ } else {
12801
+ // Split startContainer text node if needed
12802
+ if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
12803
+ startContainer = splitText(startContainer, startOffset);
12804
+ startOffset = 0;
12805
+ }
12806
+
12807
+ // Split endContainer text node if needed
12808
+ if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
12809
+ endContainer = splitText(endContainer, endOffset).previousSibling;
12810
+ endOffset = endContainer.nodeValue.length;
12811
+ }
12812
+ }
12813
+
12814
+ return {
12815
+ startContainer: startContainer,
12816
+ startOffset: startOffset,
12817
+ endContainer: endContainer,
12818
+ endOffset: endOffset
12819
+ };
12820
+ };
12821
+
12822
+ /**
12823
+ * Normalizes the specified range by finding the closest best suitable caret location.
12824
+ *
12825
+ * @private
12826
+ * @param {Range} rng Range to normalize.
12827
+ * @return {Boolean} True/false if the specified range was normalized or not.
12828
+ */
12829
+ this.normalize = function(rng) {
12830
+ var normalized, collapsed;
12831
+
12832
+ function normalizeEndPoint(start) {
12833
+ var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
12834
+ var directionLeft, isAfterNode;
12835
+
12836
+ function hasBrBeforeAfter(node, left) {
12837
+ var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
12838
+
12839
+ while ((node = walker[left ? 'prev' : 'next']())) {
12840
+ if (node.nodeName === "BR") {
12841
+ return true;
12842
+ }
12843
+ }
12844
+ }
12845
+
12846
+ function isPrevNode(node, name) {
12847
+ return node.previousSibling && node.previousSibling.nodeName == name;
12848
+ }
12849
+
12850
+ // Walks the dom left/right to find a suitable text node to move the endpoint into
12851
+ // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
12852
+ function findTextNodeRelative(left, startNode) {
12853
+ var walker, lastInlineElement, parentBlockContainer;
12854
+
12855
+ startNode = startNode || container;
12856
+ parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
12857
+
12858
+ // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
12859
+ // This: <p><br>|</p> becomes <p>|<br></p>
12860
+ if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
12861
+ container = startNode.parentNode;
12862
+ offset = dom.nodeIndex(startNode);
12863
+ normalized = true;
12864
+ return;
12865
+ }
12866
+
12867
+ // Walk left until we hit a text node we can move to or a block/br/img
12868
+ walker = new TreeWalker(startNode, parentBlockContainer);
12869
+ while ((node = walker[left ? 'prev' : 'next']())) {
12870
+ // Found text node that has a length
12871
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
12872
+ container = node;
12873
+ offset = left ? node.nodeValue.length : 0;
12874
+ normalized = true;
12875
+ return;
12876
+ }
12877
+
12878
+ // Break if we find a block or a BR/IMG/INPUT etc
12879
+ if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
12880
+ return;
12881
+ }
12882
+
12883
+ lastInlineElement = node;
12884
+ }
12885
+
12886
+ // Only fetch the last inline element when in caret mode for now
12887
+ if (collapsed && lastInlineElement) {
12888
+ container = lastInlineElement;
12889
+ normalized = true;
12890
+ offset = 0;
12891
+ }
12892
+ }
12893
+
12894
+ container = rng[(start ? 'start' : 'end') + 'Container'];
12895
+ offset = rng[(start ? 'start' : 'end') + 'Offset'];
12896
+ isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
12897
+ nonEmptyElementsMap = dom.schema.getNonEmptyElements();
12898
+ directionLeft = start;
12899
+
12900
+ if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
12901
+ directionLeft = false;
12902
+ }
12903
+
12904
+ // If the container is a document move it to the body element
12905
+ if (container.nodeType === 9) {
12906
+ container = dom.getRoot();
12907
+ offset = 0;
12908
+ }
12909
+
12910
+ // If the container is body try move it into the closest text node or position
12911
+ if (container === body) {
12912
+ // If start is before/after a image, table etc
12913
+ if (directionLeft) {
12914
+ node = container.childNodes[offset > 0 ? offset - 1 : 0];
12915
+ if (node) {
12916
+ nodeName = node.nodeName.toLowerCase();
12917
+ if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
12918
+ return;
12919
+ }
12920
+ }
12921
+ }
12922
+
12923
+ // Resolve the index
12924
+ if (container.hasChildNodes()) {
12925
+ offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
12926
+ container = container.childNodes[offset];
12927
+ offset = 0;
12928
+
12929
+ // Don't walk into elements that doesn't have any child nodes like a IMG
12930
+ if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
12931
+ // Walk the DOM to find a text node to place the caret at or a BR
12932
+ node = container;
12933
+ walker = new TreeWalker(container, body);
12934
+
12935
+ do {
12936
+ // Found a text node use that position
12937
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
12938
+ offset = directionLeft ? 0 : node.nodeValue.length;
12939
+ container = node;
12940
+ normalized = true;
12941
+ break;
12942
+ }
12943
+
12944
+ // Found a BR/IMG element that we can place the caret before
12945
+ if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
12946
+ offset = dom.nodeIndex(node);
12947
+ container = node.parentNode;
12948
+
12949
+ // Put caret after image when moving the end point
12950
+ if (node.nodeName == "IMG" && !directionLeft) {
12951
+ offset++;
12952
+ }
12953
+
12954
+ normalized = true;
12955
+ break;
12956
+ }
12957
+ } while ((node = (directionLeft ? walker.next() : walker.prev())));
12958
+ }
12959
+ }
12960
+ }
12961
+
12962
+ // Lean the caret to the left if possible
12963
+ if (collapsed) {
12964
+ // So this: <b>x</b><i>|x</i>
12965
+ // Becomes: <b>x|</b><i>x</i>
12966
+ // Seems that only gecko has issues with this
12967
+ if (container.nodeType === 3 && offset === 0) {
12968
+ findTextNodeRelative(true);
12969
+ }
12970
+
12971
+ // Lean left into empty inline elements when the caret is before a BR
12972
+ // So this: <i><b></b><i>|<br></i>
12973
+ // Becomes: <i><b>|</b><i><br></i>
12974
+ // Seems that only gecko has issues with this.
12975
+ // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
12976
+ if (container.nodeType === 1) {
12977
+ node = container.childNodes[offset];
12978
+
12979
+ // Offset is after the containers last child
12980
+ // then use the previous child for normalization
12981
+ if (!node) {
12982
+ node = container.childNodes[offset - 1];
12983
+ }
12984
+
12985
+ if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
12986
+ !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
12987
+ findTextNodeRelative(true, node);
12988
+ }
12989
+ }
12990
+ }
12991
+
12992
+ // Lean the start of the selection right if possible
12993
+ // So this: x[<b>x]</b>
12994
+ // Becomes: x<b>[x]</b>
12995
+ if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
12996
+ findTextNodeRelative(false);
12997
+ }
12998
+
12999
+ // Set endpoint if it was normalized
13000
+ if (normalized) {
13001
+ rng['set' + (start ? 'Start' : 'End')](container, offset);
13002
+ }
13003
+ }
13004
+
13005
+ collapsed = rng.collapsed;
13006
+
13007
+ normalizeEndPoint(true);
13008
+
13009
+ if (!collapsed) {
13010
+ normalizeEndPoint();
13011
+ }
13012
+
13013
+ // If it was collapsed then make sure it still is
13014
+ if (normalized && collapsed) {
13015
+ rng.collapse(true);
13016
+ }
13017
+
13018
+ return normalized;
13019
+ };
13020
+ }
13021
+
13022
+ /**
13023
+ * Compares two ranges and checks if they are equal.
13024
+ *
13025
+ * @static
13026
+ * @method compareRanges
13027
+ * @param {DOMRange} rng1 First range to compare.
13028
+ * @param {DOMRange} rng2 First range to compare.
13029
+ * @return {Boolean} true/false if the ranges are equal.
13030
+ */
13031
+ RangeUtils.compareRanges = function(rng1, rng2) {
13032
+ if (rng1 && rng2) {
13033
+ // Compare native IE ranges
13034
+ if (rng1.item || rng1.duplicate) {
13035
+ // Both are control ranges and the selected element matches
13036
+ if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
13037
+ return true;
13038
+ }
13039
+
13040
+ // Both are text ranges and the range matches
13041
+ if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
13042
+ return true;
13043
+ }
13044
+ } else {
13045
+ // Compare w3c ranges
13046
+ return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
13047
+ }
13048
+ }
13049
+
13050
+ return false;
13051
+ };
13052
+
13053
+ return RangeUtils;
13054
+ });
13055
+
12571
13056
  // Included from: js/tinymce/classes/dom/Selection.js
12572
13057
 
12573
13058
  /**
@@ -12593,9 +13078,10 @@ define("tinymce/dom/Selection", [
12593
13078
  "tinymce/dom/TreeWalker",
12594
13079
  "tinymce/dom/TridentSelection",
12595
13080
  "tinymce/dom/ControlSelection",
13081
+ "tinymce/dom/RangeUtils",
12596
13082
  "tinymce/Env",
12597
13083
  "tinymce/util/Tools"
12598
- ], function(TreeWalker, TridentSelection, ControlSelection, Env, Tools) {
13084
+ ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, Env, Tools) {
12599
13085
  var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
12600
13086
  var isIE = Env.ie, isOpera = Env.opera;
12601
13087
 
@@ -13345,6 +13831,19 @@ define("tinymce/dom/Selection", [
13345
13831
  getRng: function(w3c) {
13346
13832
  var self = this, selection, rng, elm, doc = self.win.document, ieRng;
13347
13833
 
13834
+ function tryCompareBounderyPoints(how, sourceRange, destinationRange) {
13835
+ try {
13836
+ return sourceRange.compareBoundaryPoints(how, destinationRange);
13837
+ } catch (ex) {
13838
+ // Gecko throws wrong document exception if the range points
13839
+ // to nodes that where removed from the dom #6690
13840
+ // Browsers should mutate existing DOMRange instances so that they always point
13841
+ // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
13842
+ // For performance reasons just return -1
13843
+ return -1;
13844
+ }
13845
+ }
13846
+
13348
13847
  // Use last rng passed from FocusManager if it's available this enables
13349
13848
  // calls to editor.selection.getStart() to work when caret focus is lost on IE
13350
13849
  if (!w3c && self.lastFocusBookmark) {
@@ -13412,8 +13911,8 @@ define("tinymce/dom/Selection", [
13412
13911
  }
13413
13912
 
13414
13913
  if (self.selectedRange && self.explicitRange) {
13415
- if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 &&
13416
- rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
13914
+ if (tryCompareBounderyPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
13915
+ tryCompareBounderyPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
13417
13916
  // Safari, Opera and Chrome only ever select text which causes the range to change.
13418
13917
  // This lets us use the originally set range if the selection hasn't been changed by the user.
13419
13918
  rng = self.explicitRange;
@@ -13612,7 +14111,7 @@ define("tinymce/dom/Selection", [
13612
14111
  return selectedBlocks;
13613
14112
  },
13614
14113
 
13615
- isForward: function(){
14114
+ isForward: function() {
13616
14115
  var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
13617
14116
 
13618
14117
  // No support for selection direction then always return true
@@ -13632,188 +14131,13 @@ define("tinymce/dom/Selection", [
13632
14131
  },
13633
14132
 
13634
14133
  normalize: function() {
13635
- var self = this, rng, normalized, collapsed;
13636
-
13637
- function normalizeEndPoint(start) {
13638
- var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
13639
- var directionLeft;
13640
-
13641
- function hasBrBeforeAfter(node, left) {
13642
- var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
13643
-
13644
- while ((node = walker[left ? 'prev' : 'next']())) {
13645
- if (node.nodeName === "BR") {
13646
- return true;
13647
- }
13648
- }
13649
- }
13650
-
13651
- function isPrevNode(node, name) {
13652
- return node.previousSibling && node.previousSibling.nodeName == name;
13653
- }
13654
-
13655
- // Walks the dom left/right to find a suitable text node to move the endpoint into
13656
- // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
13657
- function findTextNodeRelative(left, startNode) {
13658
- var walker, lastInlineElement;
13659
-
13660
- startNode = startNode || container;
13661
- walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
13662
-
13663
- // Walk left until we hit a text node we can move to or a block/br/img
13664
- while ((node = walker[left ? 'prev' : 'next']())) {
13665
- // Found text node that has a length
13666
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
13667
- container = node;
13668
- offset = left ? node.nodeValue.length : 0;
13669
- normalized = true;
13670
- return;
13671
- }
13672
-
13673
- // Break if we find a block or a BR/IMG/INPUT etc
13674
- if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
13675
- return;
13676
- }
13677
-
13678
- lastInlineElement = node;
13679
- }
14134
+ var self = this, rng = self.getRng();
13680
14135
 
13681
- // Only fetch the last inline element when in caret mode for now
13682
- if (collapsed && lastInlineElement) {
13683
- container = lastInlineElement;
13684
- normalized = true;
13685
- offset = 0;
13686
- }
13687
- }
13688
-
13689
- container = rng[(start ? 'start' : 'end') + 'Container'];
13690
- offset = rng[(start ? 'start' : 'end') + 'Offset'];
13691
- nonEmptyElementsMap = dom.schema.getNonEmptyElements();
13692
- directionLeft = start;
13693
-
13694
- if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
13695
- directionLeft = false;
13696
- }
13697
-
13698
- // If the container is a document move it to the body element
13699
- if (container.nodeType === 9) {
13700
- container = dom.getRoot();
13701
- offset = 0;
13702
- }
13703
-
13704
- // If the container is body try move it into the closest text node or position
13705
- if (container === body) {
13706
- // If start is before/after a image, table etc
13707
- if (directionLeft) {
13708
- node = container.childNodes[offset > 0 ? offset - 1 : 0];
13709
- if (node) {
13710
- nodeName = node.nodeName.toLowerCase();
13711
- if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
13712
- return;
13713
- }
13714
- }
13715
- }
13716
-
13717
- // Resolve the index
13718
- if (container.hasChildNodes()) {
13719
- offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
13720
- container = container.childNodes[offset];
13721
- offset = 0;
13722
-
13723
- // Don't walk into elements that doesn't have any child nodes like a IMG
13724
- if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
13725
- // Walk the DOM to find a text node to place the caret at or a BR
13726
- node = container;
13727
- walker = new TreeWalker(container, body);
13728
-
13729
- do {
13730
- // Found a text node use that position
13731
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
13732
- offset = directionLeft ? 0 : node.nodeValue.length;
13733
- container = node;
13734
- normalized = true;
13735
- break;
13736
- }
13737
-
13738
- // Found a BR/IMG element that we can place the caret before
13739
- if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
13740
- offset = dom.nodeIndex(node);
13741
- container = node.parentNode;
13742
-
13743
- // Put caret after image when moving the end point
13744
- if (node.nodeName == "IMG" && !directionLeft) {
13745
- offset++;
13746
- }
13747
-
13748
- normalized = true;
13749
- break;
13750
- }
13751
- } while ((node = (directionLeft ? walker.next() : walker.prev())));
13752
- }
13753
- }
13754
- }
13755
-
13756
- // Lean the caret to the left if possible
13757
- if (collapsed) {
13758
- // So this: <b>x</b><i>|x</i>
13759
- // Becomes: <b>x|</b><i>x</i>
13760
- // Seems that only gecko has issues with this
13761
- if (container.nodeType === 3 && offset === 0) {
13762
- findTextNodeRelative(true);
13763
- }
13764
-
13765
- // Lean left into empty inline elements when the caret is before a BR
13766
- // So this: <i><b></b><i>|<br></i>
13767
- // Becomes: <i><b>|</b><i><br></i>
13768
- // Seems that only gecko has issues with this.
13769
- // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
13770
- if (container.nodeType === 1) {
13771
- node = container.childNodes[offset];
13772
- if(node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
13773
- !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
13774
- findTextNodeRelative(true, container.childNodes[offset]);
13775
- }
13776
- }
13777
- }
13778
-
13779
- // Lean the start of the selection right if possible
13780
- // So this: x[<b>x]</b>
13781
- // Becomes: x<b>[x]</b>
13782
- if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
13783
- findTextNodeRelative(false);
13784
- }
13785
-
13786
- // Set endpoint if it was normalized
13787
- if (normalized) {
13788
- rng['set' + (start ? 'Start' : 'End')](container, offset);
13789
- }
13790
- }
13791
-
13792
- // Normalize only on non IE browsers for now
13793
- if (isIE) {
13794
- return;
13795
- }
13796
-
13797
- rng = self.getRng();
13798
- collapsed = rng.collapsed;
13799
-
13800
- // Normalize the end points
13801
- normalizeEndPoint(true);
13802
-
13803
- if (!collapsed) {
13804
- normalizeEndPoint();
13805
- }
13806
-
13807
- // Set the selection if it was normalized
13808
- if (normalized) {
13809
- // If it was collapsed then make sure it still is
13810
- if (collapsed) {
13811
- rng.collapse(true);
13812
- }
13813
-
13814
- //console.log(self.dom.dumpRng(rng));
14136
+ if (!isIE && new RangeUtils(self.dom).normalize(rng)) {
13815
14137
  self.setRng(rng, self.isForward());
13816
14138
  }
14139
+
14140
+ return rng;
13817
14141
  },
13818
14142
 
13819
14143
  /**
@@ -13991,285 +14315,6 @@ define("tinymce/dom/Selection", [
13991
14315
  return Selection;
13992
14316
  });
13993
14317
 
13994
- // Included from: js/tinymce/classes/dom/RangeUtils.js
13995
-
13996
- /**
13997
- * Range.js
13998
- *
13999
- * Copyright, Moxiecode Systems AB
14000
- * Released under LGPL License.
14001
- *
14002
- * License: http://www.tinymce.com/license
14003
- * Contributing: http://www.tinymce.com/contributing
14004
- */
14005
-
14006
- /**
14007
- * RangeUtils
14008
- *
14009
- * @class tinymce.dom.RangeUtils
14010
- * @private
14011
- */
14012
- define("tinymce/dom/RangeUtils", [
14013
- "tinymce/util/Tools"
14014
- ], function(Tools) {
14015
- var each = Tools.each;
14016
-
14017
- function RangeUtils(dom) {
14018
- /**
14019
- * Walks the specified range like object and executes the callback for each sibling collection it finds.
14020
- *
14021
- * @method walk
14022
- * @param {Object} rng Range like object.
14023
- * @param {function} callback Callback function to execute for each sibling collection.
14024
- */
14025
- this.walk = function(rng, callback) {
14026
- var startContainer = rng.startContainer,
14027
- startOffset = rng.startOffset,
14028
- endContainer = rng.endContainer,
14029
- endOffset = rng.endOffset,
14030
- ancestor, startPoint,
14031
- endPoint, node, parent, siblings, nodes;
14032
-
14033
- // Handle table cell selection the table plugin enables
14034
- // you to fake select table cells and perform formatting actions on them
14035
- nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
14036
- if (nodes.length > 0) {
14037
- each(nodes, function(node) {
14038
- callback([node]);
14039
- });
14040
-
14041
- return;
14042
- }
14043
-
14044
- /**
14045
- * Excludes start/end text node if they are out side the range
14046
- *
14047
- * @private
14048
- * @param {Array} nodes Nodes to exclude items from.
14049
- * @return {Array} Array with nodes excluding the start/end container if needed.
14050
- */
14051
- function exclude(nodes) {
14052
- var node;
14053
-
14054
- // First node is excluded
14055
- node = nodes[0];
14056
- if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
14057
- nodes.splice(0, 1);
14058
- }
14059
-
14060
- // Last node is excluded
14061
- node = nodes[nodes.length - 1];
14062
- if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
14063
- nodes.splice(nodes.length - 1, 1);
14064
- }
14065
-
14066
- return nodes;
14067
- }
14068
-
14069
- /**
14070
- * Collects siblings
14071
- *
14072
- * @private
14073
- * @param {Node} node Node to collect siblings from.
14074
- * @param {String} name Name of the sibling to check for.
14075
- * @return {Array} Array of collected siblings.
14076
- */
14077
- function collectSiblings(node, name, end_node) {
14078
- var siblings = [];
14079
-
14080
- for (; node && node != end_node; node = node[name]) {
14081
- siblings.push(node);
14082
- }
14083
-
14084
- return siblings;
14085
- }
14086
-
14087
- /**
14088
- * Find an end point this is the node just before the common ancestor root.
14089
- *
14090
- * @private
14091
- * @param {Node} node Node to start at.
14092
- * @param {Node} root Root/ancestor element to stop just before.
14093
- * @return {Node} Node just before the root element.
14094
- */
14095
- function findEndPoint(node, root) {
14096
- do {
14097
- if (node.parentNode == root) {
14098
- return node;
14099
- }
14100
-
14101
- node = node.parentNode;
14102
- } while(node);
14103
- }
14104
-
14105
- function walkBoundary(start_node, end_node, next) {
14106
- var siblingName = next ? 'nextSibling' : 'previousSibling';
14107
-
14108
- for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
14109
- parent = node.parentNode;
14110
- siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
14111
-
14112
- if (siblings.length) {
14113
- if (!next) {
14114
- siblings.reverse();
14115
- }
14116
-
14117
- callback(exclude(siblings));
14118
- }
14119
- }
14120
- }
14121
-
14122
- // If index based start position then resolve it
14123
- if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
14124
- startContainer = startContainer.childNodes[startOffset];
14125
- }
14126
-
14127
- // If index based end position then resolve it
14128
- if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
14129
- endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
14130
- }
14131
-
14132
- // Same container
14133
- if (startContainer == endContainer) {
14134
- return callback(exclude([startContainer]));
14135
- }
14136
-
14137
- // Find common ancestor and end points
14138
- ancestor = dom.findCommonAncestor(startContainer, endContainer);
14139
-
14140
- // Process left side
14141
- for (node = startContainer; node; node = node.parentNode) {
14142
- if (node === endContainer) {
14143
- return walkBoundary(startContainer, ancestor, true);
14144
- }
14145
-
14146
- if (node === ancestor) {
14147
- break;
14148
- }
14149
- }
14150
-
14151
- // Process right side
14152
- for (node = endContainer; node; node = node.parentNode) {
14153
- if (node === startContainer) {
14154
- return walkBoundary(endContainer, ancestor);
14155
- }
14156
-
14157
- if (node === ancestor) {
14158
- break;
14159
- }
14160
- }
14161
-
14162
- // Find start/end point
14163
- startPoint = findEndPoint(startContainer, ancestor) || startContainer;
14164
- endPoint = findEndPoint(endContainer, ancestor) || endContainer;
14165
-
14166
- // Walk left leaf
14167
- walkBoundary(startContainer, startPoint, true);
14168
-
14169
- // Walk the middle from start to end point
14170
- siblings = collectSiblings(
14171
- startPoint == startContainer ? startPoint : startPoint.nextSibling,
14172
- 'nextSibling',
14173
- endPoint == endContainer ? endPoint.nextSibling : endPoint
14174
- );
14175
-
14176
- if (siblings.length) {
14177
- callback(exclude(siblings));
14178
- }
14179
-
14180
- // Walk right leaf
14181
- walkBoundary(endContainer, endPoint);
14182
- };
14183
-
14184
- /**
14185
- * Splits the specified range at it's start/end points.
14186
- *
14187
- * @private
14188
- * @param {Range/RangeObject} rng Range to split.
14189
- * @return {Object} Range position object.
14190
- */
14191
- this.split = function(rng) {
14192
- var startContainer = rng.startContainer,
14193
- startOffset = rng.startOffset,
14194
- endContainer = rng.endContainer,
14195
- endOffset = rng.endOffset;
14196
-
14197
- function splitText(node, offset) {
14198
- return node.splitText(offset);
14199
- }
14200
-
14201
- // Handle single text node
14202
- if (startContainer == endContainer && startContainer.nodeType == 3) {
14203
- if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
14204
- endContainer = splitText(startContainer, startOffset);
14205
- startContainer = endContainer.previousSibling;
14206
-
14207
- if (endOffset > startOffset) {
14208
- endOffset = endOffset - startOffset;
14209
- startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
14210
- endOffset = endContainer.nodeValue.length;
14211
- startOffset = 0;
14212
- } else {
14213
- endOffset = 0;
14214
- }
14215
- }
14216
- } else {
14217
- // Split startContainer text node if needed
14218
- if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
14219
- startContainer = splitText(startContainer, startOffset);
14220
- startOffset = 0;
14221
- }
14222
-
14223
- // Split endContainer text node if needed
14224
- if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
14225
- endContainer = splitText(endContainer, endOffset).previousSibling;
14226
- endOffset = endContainer.nodeValue.length;
14227
- }
14228
- }
14229
-
14230
- return {
14231
- startContainer: startContainer,
14232
- startOffset: startOffset,
14233
- endContainer: endContainer,
14234
- endOffset: endOffset
14235
- };
14236
- };
14237
- }
14238
-
14239
- /**
14240
- * Compares two ranges and checks if they are equal.
14241
- *
14242
- * @static
14243
- * @method compareRanges
14244
- * @param {DOMRange} rng1 First range to compare.
14245
- * @param {DOMRange} rng2 First range to compare.
14246
- * @return {Boolean} true/false if the ranges are equal.
14247
- */
14248
- RangeUtils.compareRanges = function(rng1, rng2) {
14249
- if (rng1 && rng2) {
14250
- // Compare native IE ranges
14251
- if (rng1.item || rng1.duplicate) {
14252
- // Both are control ranges and the selected element matches
14253
- if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
14254
- return true;
14255
- }
14256
-
14257
- // Both are text ranges and the range matches
14258
- if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
14259
- return true;
14260
- }
14261
- } else {
14262
- // Compare w3c ranges
14263
- return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
14264
- }
14265
- }
14266
-
14267
- return false;
14268
- };
14269
-
14270
- return RangeUtils;
14271
- });
14272
-
14273
14318
  // Included from: js/tinymce/classes/Formatter.js
14274
14319
 
14275
14320
  /**
@@ -14415,7 +14460,7 @@ define("tinymce/Formatter", [
14415
14460
 
14416
14461
  removeformat: [
14417
14462
  {
14418
- selector: 'b,strong,em,i,font,u,strike,sub,sup',
14463
+ selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q',
14419
14464
  remove: 'all',
14420
14465
  split: true,
14421
14466
  expand: false,
@@ -14830,7 +14875,7 @@ define("tinymce/Formatter", [
14830
14875
  });
14831
14876
 
14832
14877
  // If child was found and of the same type as the current node
14833
- if (child && matchName(child, format)) {
14878
+ if (child && !isBookmarkNode(child) && matchName(child, format)) {
14834
14879
  clone = dom.clone(child, FALSE);
14835
14880
  setElementFormat(clone);
14836
14881
 
@@ -14865,6 +14910,10 @@ define("tinymce/Formatter", [
14865
14910
  each(dom.select(format.inline, node), function(child) {
14866
14911
  var parent;
14867
14912
 
14913
+ if (isBookmarkNode(child)) {
14914
+ return;
14915
+ }
14916
+
14868
14917
  // When wrap_links is set to false we don't want
14869
14918
  // to remove the format on children within links
14870
14919
  if (format.wrap_links === false) {
@@ -16166,7 +16215,7 @@ define("tinymce/Formatter", [
16166
16215
  next = next ? 'nextSibling' : 'previousSibling';
16167
16216
 
16168
16217
  for (node = inc ? node : node[next]; node; node = node[next]) {
16169
- if (node.nodeType == 1 && !isWhiteSpaceNode(node)) {
16218
+ if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
16170
16219
  return node;
16171
16220
  }
16172
16221
  }
@@ -16284,7 +16333,7 @@ define("tinymce/Formatter", [
16284
16333
  return FALSE;
16285
16334
  }
16286
16335
 
16287
- return TRUE;
16336
+ return !isBookmarkNode(node1) && !isBookmarkNode(node2);
16288
16337
  }
16289
16338
 
16290
16339
  function findElementSibling(node, sibling_name) {
@@ -16731,9 +16780,9 @@ define("tinymce/UndoManager", [
16731
16780
  return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, ''));
16732
16781
  }
16733
16782
 
16734
- function addNonTypingUndoLevel() {
16783
+ function addNonTypingUndoLevel(e) {
16735
16784
  self.typing = false;
16736
- self.add();
16785
+ self.add({}, e);
16737
16786
  }
16738
16787
 
16739
16788
  // Add initial undo level when the editor is initialized
@@ -16755,7 +16804,7 @@ define("tinymce/UndoManager", [
16755
16804
  var cmd = e.command;
16756
16805
 
16757
16806
  if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
16758
- self.add();
16807
+ addNonTypingUndoLevel(e);
16759
16808
  }
16760
16809
  });
16761
16810
 
@@ -16763,13 +16812,8 @@ define("tinymce/UndoManager", [
16763
16812
  self.beforeChange();
16764
16813
  });
16765
16814
 
16766
- editor.on('SaveContent ObjectResized', addNonTypingUndoLevel);
16815
+ editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
16767
16816
  editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
16768
- editor.dom.bind(editor.getBody(), 'focusout', function() {
16769
- if (!editor.removed && self.typing) {
16770
- addNonTypingUndoLevel();
16771
- }
16772
- });
16773
16817
 
16774
16818
  editor.on('KeyUp', function(e) {
16775
16819
  var keyCode = e.keyCode;
@@ -16807,7 +16851,7 @@ define("tinymce/UndoManager", [
16807
16851
  // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
16808
16852
  if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
16809
16853
  if (self.typing) {
16810
- addNonTypingUndoLevel();
16854
+ addNonTypingUndoLevel(e);
16811
16855
  }
16812
16856
 
16813
16857
  return;
@@ -16817,14 +16861,14 @@ define("tinymce/UndoManager", [
16817
16861
  if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
16818
16862
  self.beforeChange();
16819
16863
  self.typing = true;
16820
- self.add();
16864
+ self.add({}, e);
16821
16865
  isFirstTypedCharacter = true;
16822
16866
  }
16823
16867
  });
16824
16868
 
16825
- editor.on('MouseDown', function() {
16869
+ editor.on('MouseDown', function(e) {
16826
16870
  if (self.typing) {
16827
- addNonTypingUndoLevel();
16871
+ addNonTypingUndoLevel(e);
16828
16872
  }
16829
16873
  });
16830
16874
 
@@ -16866,16 +16910,21 @@ define("tinymce/UndoManager", [
16866
16910
  * Adds a new undo level/snapshot to the undo list.
16867
16911
  *
16868
16912
  * @method add
16869
- * @param {Object} l Optional undo level object to add.
16913
+ * @param {Object} level Optional undo level object to add.
16914
+ * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
16870
16915
  * @return {Object} Undo level that got added or null it a level wasn't needed.
16871
16916
  */
16872
- add: function(level) {
16917
+ add: function(level, event) {
16873
16918
  var i, settings = editor.settings, lastLevel;
16874
16919
 
16875
16920
  level = level || {};
16876
16921
  level.content = getContent();
16877
16922
 
16878
- if (lock || editor.fire('BeforeAddUndo', {level: level}).isDefaultPrevented()) {
16923
+ if (lock || editor.removed) {
16924
+ return null;
16925
+ }
16926
+
16927
+ if (editor.fire('BeforeAddUndo', {level: level, originalEvent: event}).isDefaultPrevented()) {
16879
16928
  return null;
16880
16929
  }
16881
16930
 
@@ -16913,13 +16962,13 @@ define("tinymce/UndoManager", [
16913
16962
  data.push(level);
16914
16963
  index = data.length - 1;
16915
16964
 
16916
- var args = {level: level, lastLevel: lastLevel};
16965
+ var args = {level: level, lastLevel: lastLevel, originalEvent: event};
16917
16966
 
16918
16967
  editor.fire('AddUndo', args);
16919
16968
 
16920
16969
  if (index > 0) {
16921
- editor.fire('change', args);
16922
16970
  editor.isNotDirty = false;
16971
+ editor.fire('change', args);
16923
16972
  }
16924
16973
 
16925
16974
  return level;
@@ -17051,8 +17100,9 @@ define("tinymce/UndoManager", [
17051
17100
  */
17052
17101
  define("tinymce/EnterKey", [
17053
17102
  "tinymce/dom/TreeWalker",
17103
+ "tinymce/dom/RangeUtils",
17054
17104
  "tinymce/Env"
17055
- ], function(TreeWalker, Env) {
17105
+ ], function(TreeWalker, RangeUtils, Env) {
17056
17106
  var isIE = Env.ie && Env.ie < 11;
17057
17107
 
17058
17108
  return function(editor) {
@@ -17060,7 +17110,7 @@ define("tinymce/EnterKey", [
17060
17110
  var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
17061
17111
 
17062
17112
  function handleEnterKey(evt) {
17063
- var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
17113
+ var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
17064
17114
  newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
17065
17115
 
17066
17116
  // Returns true if the block can be split into two blocks or not
@@ -17225,7 +17275,7 @@ define("tinymce/EnterKey", [
17225
17275
  // Clone any parent styles
17226
17276
  if (settings.keep_styles !== false) {
17227
17277
  do {
17228
- if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
17278
+ if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U|VAR|CITE|DFN|CODE|MARK|Q|SUP|SUB|SAMP)$/.test(node.nodeName)) {
17229
17279
  // Never clone a caret containers
17230
17280
  if (node.id == '_mce_caret') {
17231
17281
  continue;
@@ -17542,18 +17592,21 @@ define("tinymce/EnterKey", [
17542
17592
  }
17543
17593
  }
17544
17594
 
17545
- // Delete any selected contents
17546
- if (!rng.collapsed) {
17547
- editor.execCommand('Delete');
17548
- return;
17549
- }
17595
+ rng = selection.getRng(true);
17550
17596
 
17551
17597
  // Event is blocked by some other handler for example the lists plugin
17552
17598
  if (evt.isDefaultPrevented()) {
17553
17599
  return;
17554
17600
  }
17555
17601
 
17602
+ // Delete any selected contents
17603
+ if (!rng.collapsed) {
17604
+ editor.execCommand('Delete');
17605
+ return;
17606
+ }
17607
+
17556
17608
  // Setup range items and newBlockName
17609
+ new RangeUtils(dom).normalize(rng);
17557
17610
  container = rng.startContainer;
17558
17611
  offset = rng.startOffset;
17559
17612
  newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
@@ -17564,6 +17617,7 @@ define("tinymce/EnterKey", [
17564
17617
  // Resolve node index
17565
17618
  if (container.nodeType == 1 && container.hasChildNodes()) {
17566
17619
  isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
17620
+
17567
17621
  container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
17568
17622
  if (isAfterLastNodeInContainer && container.nodeType == 3) {
17569
17623
  offset = container.nodeValue.length;
@@ -20070,7 +20124,7 @@ define("tinymce/ui/Control", [
20070
20124
  self.settings = settings = Tools.extend({}, self.Defaults, settings);
20071
20125
 
20072
20126
  // Initial states
20073
- self._id = DomUtils.id();
20127
+ self._id = settings.id || DomUtils.id();
20074
20128
  self._text = self._name = '';
20075
20129
  self._width = self._height = 0;
20076
20130
  self._aria = {role: settings.role};
@@ -21026,7 +21080,7 @@ define("tinymce/ui/Control", [
21026
21080
  * @return {tinymce.ui.Control} Current control instance.
21027
21081
  */
21028
21082
  aria: function(name, value) {
21029
- var self = this, elm = self.getEl();
21083
+ var self = this, elm = self.getEl(self.ariaTarget);
21030
21084
 
21031
21085
  if (typeof(value) === "undefined") {
21032
21086
  return self._aria[name];
@@ -21035,10 +21089,6 @@ define("tinymce/ui/Control", [
21035
21089
  }
21036
21090
 
21037
21091
  if (self._rendered) {
21038
- if (name == 'label') {
21039
- elm.setAttribute('aria-labelledby', self._id);
21040
- }
21041
-
21042
21092
  elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
21043
21093
  }
21044
21094
 
@@ -21669,6 +21719,399 @@ define("tinymce/ui/Factory", [], function() {
21669
21719
  };
21670
21720
  });
21671
21721
 
21722
+ // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
21723
+
21724
+ /**
21725
+ * KeyboardNavigation.js
21726
+ *
21727
+ * Copyright, Moxiecode Systems AB
21728
+ * Released under LGPL License.
21729
+ *
21730
+ * License: http://www.tinymce.com/license
21731
+ * Contributing: http://www.tinymce.com/contributing
21732
+ */
21733
+
21734
+ /**
21735
+ * This class handles keyboard navigation of controls and elements.
21736
+ *
21737
+ * @class tinymce.ui.KeyboardNavigation
21738
+ */
21739
+ define("tinymce/ui/KeyboardNavigation", [
21740
+ ], function() {
21741
+ "use strict";
21742
+
21743
+ /**
21744
+ * This class handles all keyboard navigation for WAI-ARIA support. Each root container
21745
+ * gets an instance of this class.
21746
+ *
21747
+ * @constructor
21748
+ */
21749
+ return function(settings) {
21750
+ var root = settings.root, focusedElement, focusedControl;
21751
+
21752
+ focusedElement = document.activeElement;
21753
+ focusedControl = root.getParentCtrl(focusedElement);
21754
+
21755
+ /**
21756
+ * Returns the currently focused elements wai aria role of the currently
21757
+ * focused element or specified element.
21758
+ *
21759
+ * @private
21760
+ * @param {Element} elm Optional element to get role from.
21761
+ * @return {String} Role of specified element.
21762
+ */
21763
+ function getRole(elm) {
21764
+ elm = elm || focusedElement;
21765
+
21766
+ return elm && elm.getAttribute('role');
21767
+ }
21768
+
21769
+ /**
21770
+ * Returns the wai role of the parent element of the currently
21771
+ * focused element or specified element.
21772
+ *
21773
+ * @private
21774
+ * @param {Element} elm Optional element to get parent role from.
21775
+ * @return {String} Role of the first parent that has a role.
21776
+ */
21777
+ function getParentRole(elm) {
21778
+ var role, parent = elm || focusedElement;
21779
+
21780
+ while ((parent = parent.parentNode)) {
21781
+ if ((role = getRole(parent))) {
21782
+ return role;
21783
+ }
21784
+ }
21785
+ }
21786
+
21787
+ /**
21788
+ * Returns a wai aria property by name for example aria-selected.
21789
+ *
21790
+ * @private
21791
+ * @param {String} name Name of the aria property to get for example "disabled".
21792
+ * @return {String} Aria property value.
21793
+ */
21794
+ function getAriaProp(name) {
21795
+ var elm = focusedElement;
21796
+
21797
+ if (elm) {
21798
+ return elm.getAttribute('aria-' + name);
21799
+ }
21800
+ }
21801
+
21802
+ /**
21803
+ * Is the element a text input element or not.
21804
+ *
21805
+ * @private
21806
+ * @param {Element} elm Element to check if it's an text input element or not.
21807
+ * @return {Boolean} True/false if the element is a text element or not.
21808
+ */
21809
+ function isTextInputElement(elm) {
21810
+ // Notice: since type can be "email" etc we don't check the type
21811
+ // So all input elements gets treated as text input elements
21812
+ return elm.tagName == "INPUT" || elm.tagName == "TEXTAREA";
21813
+ }
21814
+
21815
+ /**
21816
+ * Returns true/false if the specified element can be focused or not.
21817
+ *
21818
+ * @private
21819
+ * @param {Element} elm DOM element to check if it can be focused or not.
21820
+ * @return {Boolean} True/false if the element can have focus.
21821
+ */
21822
+ function canFocus(elm) {
21823
+ if (isTextInputElement(elm) && !elm.hidden) {
21824
+ return true;
21825
+ }
21826
+
21827
+ if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
21828
+ return true;
21829
+ }
21830
+
21831
+ return false;
21832
+ }
21833
+
21834
+ /**
21835
+ * Returns an array of focusable visible elements within the specified container element.
21836
+ *
21837
+ * @private
21838
+ * @param {Element} elm DOM element to find focusable elements within.
21839
+ * @return {Array} Array of focusable elements.
21840
+ */
21841
+ function getFocusElements(elm) {
21842
+ var elements = [];
21843
+
21844
+ function collect(elm) {
21845
+ if (elm.nodeType != 1 || elm.style.display == 'none') {
21846
+ return;
21847
+ }
21848
+
21849
+ if (canFocus(elm)) {
21850
+ elements.push(elm);
21851
+ }
21852
+
21853
+ for (var i = 0; i < elm.childNodes.length; i++) {
21854
+ collect(elm.childNodes[i]);
21855
+ }
21856
+ }
21857
+
21858
+ collect(elm || root.getEl());
21859
+
21860
+ return elements;
21861
+ }
21862
+
21863
+ /**
21864
+ * Returns the navigation root control for the specified control. The navigation root
21865
+ * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
21866
+ * It will look for parents of the specified target control or the currenty focused control if this option is omitted.
21867
+ *
21868
+ * @private
21869
+ * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
21870
+ * @return {tinymce.ui.Control} Navigation root control.
21871
+ */
21872
+ function getNavigationRoot(targetControl) {
21873
+ var navigationRoot, controls;
21874
+
21875
+ targetControl = targetControl || focusedControl;
21876
+ controls = targetControl.parents().toArray();
21877
+ controls.unshift(targetControl);
21878
+
21879
+ for (var i = 0; i < controls.length; i++) {
21880
+ navigationRoot = controls[i];
21881
+
21882
+ if (navigationRoot.settings.ariaRoot) {
21883
+ break;
21884
+ }
21885
+ }
21886
+
21887
+ return navigationRoot;
21888
+ }
21889
+
21890
+ /**
21891
+ * Focuses the first item in the specified targetControl element or the last aria index if the
21892
+ * navigation root has the ariaRemember option enabled.
21893
+ *
21894
+ * @private
21895
+ * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
21896
+ */
21897
+ function focusFirst(targetControl) {
21898
+ var navigationRoot = getNavigationRoot(targetControl);
21899
+ var focusElements = getFocusElements(navigationRoot.getEl());
21900
+
21901
+ if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
21902
+ moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
21903
+ } else {
21904
+ moveFocusToIndex(0, focusElements);
21905
+ }
21906
+ }
21907
+
21908
+ /**
21909
+ * Moves the focus to the specified index within the elements list.
21910
+ * This will scope the index to the size of the element list if it changed.
21911
+ *
21912
+ * @private
21913
+ * @param {Number} idx Specified index to move to.
21914
+ * @param {Array} elements Array with dom elements to move focus within.
21915
+ * @return {Number} Input index or a changed index if it was out of range.
21916
+ */
21917
+ function moveFocusToIndex(idx, elements) {
21918
+ if (idx < 0) {
21919
+ idx = elements.length - 1;
21920
+ } else if (idx >= elements.length) {
21921
+ idx = 0;
21922
+ }
21923
+
21924
+ if (elements[idx]) {
21925
+ elements[idx].focus();
21926
+ }
21927
+
21928
+ return idx;
21929
+ }
21930
+
21931
+ /**
21932
+ * Moves the focus forwards or backwards.
21933
+ *
21934
+ * @private
21935
+ * @param {Number} dir Direction to move in positive means forward, negative means backwards.
21936
+ * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
21937
+ */
21938
+ function moveFocus(dir, elements) {
21939
+ var idx = -1, navigationRoot = getNavigationRoot();
21940
+
21941
+ elements = elements || getFocusElements(navigationRoot.getEl());
21942
+
21943
+ for (var i = 0; i < elements.length; i++) {
21944
+ if (elements[i] === focusedElement) {
21945
+ idx = i;
21946
+ }
21947
+ }
21948
+
21949
+ idx += dir;
21950
+ navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
21951
+ }
21952
+
21953
+ /**
21954
+ * Moves the focus to the left this is called by the left key.
21955
+ *
21956
+ * @private
21957
+ */
21958
+ function left() {
21959
+ var parentRole = getParentRole();
21960
+
21961
+ if (parentRole == "tablist") {
21962
+ moveFocus(-1, getFocusElements(focusedElement.parentNode));
21963
+ } else if (focusedControl.parent().submenu) {
21964
+ cancel();
21965
+ } else {
21966
+ moveFocus(-1);
21967
+ }
21968
+ }
21969
+
21970
+ /**
21971
+ * Moves the focus to the right this is called by the right key.
21972
+ *
21973
+ * @private
21974
+ */
21975
+ function right() {
21976
+ var role = getRole(), parentRole = getParentRole();
21977
+
21978
+ if (parentRole == "tablist") {
21979
+ moveFocus(1, getFocusElements(focusedElement.parentNode));
21980
+ } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
21981
+ enter();
21982
+ } else {
21983
+ moveFocus(1);
21984
+ }
21985
+ }
21986
+
21987
+ /**
21988
+ * Moves the focus to the up this is called by the up key.
21989
+ *
21990
+ * @private
21991
+ */
21992
+ function up() {
21993
+ moveFocus(-1);
21994
+ }
21995
+
21996
+ /**
21997
+ * Moves the focus to the up this is called by the down key.
21998
+ *
21999
+ * @private
22000
+ */
22001
+ function down() {
22002
+ var role = getRole(), parentRole = getParentRole();
22003
+
22004
+ if (role == "menuitem" && parentRole == "menubar") {
22005
+ enter();
22006
+ } else if (role == "button" && getAriaProp('haspopup')) {
22007
+ enter({key: 'down'});
22008
+ } else {
22009
+ moveFocus(1);
22010
+ }
22011
+ }
22012
+
22013
+ /**
22014
+ * Moves the focus to the next item or previous item depending on shift key.
22015
+ *
22016
+ * @private
22017
+ * @param {DOMEvent} e DOM event object.
22018
+ */
22019
+ function tab(e) {
22020
+ var parentRole = getParentRole();
22021
+
22022
+ if (parentRole == "tablist") {
22023
+ var elm = getFocusElements(focusedControl.getEl('body'))[0];
22024
+
22025
+ if (elm) {
22026
+ elm.focus();
22027
+ }
22028
+ } else {
22029
+ moveFocus(e.shiftKey ? -1 : 1);
22030
+ }
22031
+ }
22032
+
22033
+ /**
22034
+ * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
22035
+ *
22036
+ * @private
22037
+ */
22038
+ function cancel() {
22039
+ focusedControl.fire('cancel');
22040
+ }
22041
+
22042
+ /**
22043
+ * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
22044
+ *
22045
+ * @private
22046
+ * @param {Object} aria Optional aria data to pass along with the enter event.
22047
+ */
22048
+ function enter(aria) {
22049
+ aria = aria || {};
22050
+ focusedControl.fire('click', {target: focusedElement, aria: aria});
22051
+ }
22052
+
22053
+ root.on('keydown', function(e) {
22054
+ function handleNonTabEvent(e, handler) {
22055
+ // Ignore non tab keys for text elements
22056
+ if (isTextInputElement(focusedElement)) {
22057
+ return;
22058
+ }
22059
+
22060
+ if (handler(e) !== false) {
22061
+ e.preventDefault();
22062
+ }
22063
+ }
22064
+
22065
+ if (e.isDefaultPrevented()) {
22066
+ return;
22067
+ }
22068
+
22069
+ switch (e.keyCode) {
22070
+ case 37: // DOM_VK_LEFT
22071
+ handleNonTabEvent(e, left);
22072
+ break;
22073
+
22074
+ case 39: // DOM_VK_RIGHT
22075
+ handleNonTabEvent(e, right);
22076
+ break;
22077
+
22078
+ case 38: // DOM_VK_UP
22079
+ handleNonTabEvent(e, up);
22080
+ break;
22081
+
22082
+ case 40: // DOM_VK_DOWN
22083
+ handleNonTabEvent(e, down);
22084
+ break;
22085
+
22086
+ case 27: // DOM_VK_ESCAPE
22087
+ handleNonTabEvent(e, cancel);
22088
+ break;
22089
+
22090
+ case 14: // DOM_VK_ENTER
22091
+ case 13: // DOM_VK_RETURN
22092
+ case 32: // DOM_VK_SPACE
22093
+ handleNonTabEvent(e, enter);
22094
+ break;
22095
+
22096
+ case 9: // DOM_VK_TAB
22097
+ if (tab(e) !== false) {
22098
+ e.preventDefault();
22099
+ }
22100
+ break;
22101
+ }
22102
+ });
22103
+
22104
+ root.on('focusin', function(e) {
22105
+ focusedElement = e.target;
22106
+ focusedControl = e.control;
22107
+ });
22108
+
22109
+ return {
22110
+ focusFirst: focusFirst
22111
+ };
22112
+ };
22113
+ });
22114
+
21672
22115
  // Included from: js/tinymce/classes/ui/Container.js
21673
22116
 
21674
22117
  /**
@@ -21695,9 +22138,10 @@ define("tinymce/ui/Container", [
21695
22138
  "tinymce/ui/Collection",
21696
22139
  "tinymce/ui/Selector",
21697
22140
  "tinymce/ui/Factory",
22141
+ "tinymce/ui/KeyboardNavigation",
21698
22142
  "tinymce/util/Tools",
21699
22143
  "tinymce/ui/DomUtils"
21700
- ], function(Control, Collection, Selector, Factory, Tools, DomUtils) {
22144
+ ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
21701
22145
  "use strict";
21702
22146
 
21703
22147
  var selectorCache = {};
@@ -21788,15 +22232,41 @@ define("tinymce/ui/Container", [
21788
22232
  * for the first control in the container and focus that.
21789
22233
  *
21790
22234
  * @method focus
22235
+ * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
21791
22236
  * @return {tinymce.ui.Collection} Current instance.
21792
22237
  */
21793
- focus: function() {
21794
- var self = this;
22238
+ focus: function(keyboard) {
22239
+ var self = this, focusCtrl, keyboardNav, items;
21795
22240
 
21796
- if (self.keyNav) {
21797
- self.keyNav.focusFirst();
21798
- } else {
21799
- self._super();
22241
+ if (keyboard) {
22242
+ keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
22243
+
22244
+ if (keyboardNav) {
22245
+ keyboardNav.focusFirst(self);
22246
+ return;
22247
+ }
22248
+ }
22249
+
22250
+ items = self.find('*');
22251
+
22252
+ // TODO: Figure out a better way to auto focus alert dialog buttons
22253
+ if (self.statusbar) {
22254
+ items.add(self.statusbar.items());
22255
+ }
22256
+
22257
+ items.each(function(ctrl) {
22258
+ if (ctrl.settings.autofocus) {
22259
+ focusCtrl = null;
22260
+ return false;
22261
+ }
22262
+
22263
+ if (ctrl.canFocus) {
22264
+ focusCtrl = focusCtrl || ctrl;
22265
+ }
22266
+ });
22267
+
22268
+ if (focusCtrl) {
22269
+ focusCtrl.focus();
21800
22270
  }
21801
22271
 
21802
22272
  return self;
@@ -22059,6 +22529,12 @@ define("tinymce/ui/Container", [
22059
22529
  });
22060
22530
  }
22061
22531
 
22532
+ if (!self.parent()) {
22533
+ self.keyboardNav = new KeyboardNavigation({
22534
+ root: self
22535
+ });
22536
+ }
22537
+
22062
22538
  return self;
22063
22539
  },
22064
22540
 
@@ -22486,7 +22962,7 @@ define("tinymce/ui/Panel", [
22486
22962
  }
22487
22963
 
22488
22964
  return (
22489
- '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
22965
+ '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1" role="group">' +
22490
22966
  (self._preBodyHtml || '') +
22491
22967
  innerHtml +
22492
22968
  '</div>'
@@ -23085,6 +23561,16 @@ define("tinymce/ui/FloatPanel", [
23085
23561
  remove: function() {
23086
23562
  removeVisiblePanel(this);
23087
23563
  this._super();
23564
+ },
23565
+
23566
+ postRender: function() {
23567
+ var self = this;
23568
+
23569
+ if (self.settings.bodyRole) {
23570
+ this.getEl('body').setAttribute('role', self.settings.bodyRole);
23571
+ }
23572
+
23573
+ return self._super();
23088
23574
  }
23089
23575
  });
23090
23576
 
@@ -23100,8 +23586,7 @@ define("tinymce/ui/FloatPanel", [
23100
23586
  while (i--) {
23101
23587
  var panel = visiblePanels[i];
23102
23588
 
23103
- if (panel.settings.autohide) {
23104
- panel.fire('cancel', {}, false);
23589
+ if (panel && panel.settings.autohide) {
23105
23590
  panel.hide();
23106
23591
  visiblePanels.splice(i, 1);
23107
23592
  }
@@ -23129,347 +23614,6 @@ define("tinymce/ui/FloatPanel", [
23129
23614
  return FloatPanel;
23130
23615
  });
23131
23616
 
23132
- // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
23133
-
23134
- /**
23135
- * KeyboardNavigation.js
23136
- *
23137
- * Copyright, Moxiecode Systems AB
23138
- * Released under LGPL License.
23139
- *
23140
- * License: http://www.tinymce.com/license
23141
- * Contributing: http://www.tinymce.com/contributing
23142
- */
23143
-
23144
- /**
23145
- * This class handles keyboard navigation of controls and elements.
23146
- *
23147
- * @class tinymce.ui.KeyboardNavigation
23148
- */
23149
- define("tinymce/ui/KeyboardNavigation", [
23150
- "tinymce/ui/DomUtils"
23151
- ], function(DomUtils) {
23152
- "use strict";
23153
-
23154
- /**
23155
- * Create a new KeyboardNavigation instance to handle the focus for a specific element.
23156
- *
23157
- * @constructor
23158
- * @param {Object} settings the settings object to define how keyboard navigation works.
23159
- *
23160
- * @setting {tinymce.ui.Control} root the root control navigation focus movement is scoped to this root.
23161
- * @setting {Array} items an array containing the items to move focus between. Every object in this array must have an
23162
- * id attribute which maps to the actual DOM element and it must be able to have focus i.e. tabIndex=-1.
23163
- * @setting {Function} onCancel the callback for when the user presses escape or otherwise indicates canceling.
23164
- * @setting {Function} onAction (optional) the action handler to call when the user activates an item.
23165
- * @setting {Boolean} enableLeftRight (optional, default) when true, the up/down arrows move through items.
23166
- * @setting {Boolean} enableUpDown (optional) when true, the up/down arrows move through items.
23167
- * Note for both up/down and left/right explicitly set both enableLeftRight and enableUpDown to true.
23168
- */
23169
- return function(settings) {
23170
- var root = settings.root, enableUpDown = settings.enableUpDown !== false;
23171
- var enableLeftRight = settings.enableLeftRight !== false;
23172
- var items = settings.items, focussedId;
23173
-
23174
- /**
23175
- * Initializes the items array if needed. This will collect items/elements
23176
- * from the specified root control.
23177
- */
23178
- function initItems() {
23179
- if (!items) {
23180
- items = [];
23181
-
23182
- if (root.find) {
23183
- // Root is a container then get child elements using the UI API
23184
- root.find('*').each(function(ctrl) {
23185
- if (ctrl.canFocus) {
23186
- items.push(ctrl.getEl());
23187
- }
23188
- });
23189
- } else {
23190
- // Root is a control/widget then get the child elements of that control
23191
- var elements = root.getEl().getElementsByTagName('*');
23192
- for (var i = 0; i < elements.length; i++) {
23193
- if (elements[i].id && elements[i]) {
23194
- items.push(elements[i]);
23195
- }
23196
- }
23197
- }
23198
- }
23199
- }
23200
-
23201
- /**
23202
- * Returns the currently focused element.
23203
- *
23204
- * @private
23205
- * @return {Element} Currently focused element.
23206
- */
23207
- function getFocusElement() {
23208
- return document.getElementById(focussedId);
23209
- }
23210
-
23211
- /**
23212
- * Returns the currently focused elements wai aria role.
23213
- *
23214
- * @private
23215
- * @param {Element} elm Optional element to get role from.
23216
- * @return {String} Role of specified element.
23217
- */
23218
- function getRole(elm) {
23219
- elm = elm || getFocusElement();
23220
-
23221
- return elm && elm.getAttribute('role');
23222
- }
23223
-
23224
- /**
23225
- * Returns the role of the parent element.
23226
- *
23227
- * @private
23228
- * @param {Element} elm Optional element to get parent role from.
23229
- * @return {String} Role of the first parent that has a role.
23230
- */
23231
- function getParentRole(elm) {
23232
- var role, parent = elm || getFocusElement();
23233
-
23234
- while ((parent = parent.parentNode)) {
23235
- if ((role = getRole(parent))) {
23236
- return role;
23237
- }
23238
- }
23239
- }
23240
-
23241
- /**
23242
- * Returns an wai aria property by name.
23243
- *
23244
- * @private
23245
- * @param {String} name Name of the aria property to get for example "disabled".
23246
- * @return {String} Aria property value.
23247
- */
23248
- function getAriaProp(name) {
23249
- var elm = document.getElementById(focussedId);
23250
-
23251
- if (elm) {
23252
- return elm.getAttribute('aria-' + name);
23253
- }
23254
- }
23255
-
23256
- /**
23257
- * Executes the onAction event callback. This is when the user presses enter/space.
23258
- *
23259
- * @private
23260
- */
23261
- function action() {
23262
- var focusElm = getFocusElement();
23263
-
23264
- if (focusElm && (focusElm.nodeName == "TEXTAREA" || focusElm.type == "text")) {
23265
- return;
23266
- }
23267
-
23268
- if (settings.onAction) {
23269
- settings.onAction(focussedId);
23270
- } else {
23271
- DomUtils.fire(getFocusElement(), 'click', {keyboard: true});
23272
- }
23273
-
23274
- return true;
23275
- }
23276
-
23277
- /**
23278
- * Cancels the current navigation. The same as pressing the Esc key.
23279
- *
23280
- * @method cancel
23281
- */
23282
- function cancel() {
23283
- var focusElm;
23284
-
23285
- if (settings.onCancel) {
23286
- if ((focusElm = getFocusElement())) {
23287
- focusElm.blur();
23288
- }
23289
-
23290
- settings.onCancel();
23291
- } else {
23292
- settings.root.fire('cancel');
23293
- }
23294
- }
23295
-
23296
- /**
23297
- * Moves the focus to the next or previous item. It will wrap to start/end if it can't move.
23298
- *
23299
- * @method moveFocus
23300
- * @param {Number} dir Direction for move -1 or 1.
23301
- */
23302
- function moveFocus(dir) {
23303
- var idx = -1, focusElm, i;
23304
- var visibleItems = [];
23305
-
23306
- function isVisible(elm) {
23307
- var rootElm = root ? root.getEl() : document.body;
23308
-
23309
- while (elm && elm != rootElm) {
23310
- if (elm.style.display == 'none') {
23311
- return false;
23312
- }
23313
-
23314
- elm = elm.parentNode;
23315
- }
23316
-
23317
- return true;
23318
- }
23319
-
23320
- initItems();
23321
-
23322
- // TODO: Optimize this, will be slow on lots of items
23323
- i = visibleItems.length;
23324
- for (i = 0; i < items.length; i++) {
23325
- if (isVisible(items[i])) {
23326
- visibleItems.push(items[i]);
23327
- }
23328
- }
23329
-
23330
- i = visibleItems.length;
23331
- while (i--) {
23332
- if (visibleItems[i].id === focussedId) {
23333
- idx = i;
23334
- break;
23335
- }
23336
- }
23337
-
23338
- idx += dir;
23339
- if (idx < 0) {
23340
- idx = visibleItems.length - 1;
23341
- } else if (idx >= visibleItems.length) {
23342
- idx = 0;
23343
- }
23344
-
23345
- focusElm = visibleItems[idx];
23346
- focusElm.focus();
23347
- focussedId = focusElm.id;
23348
-
23349
- if (settings.actOnFocus) {
23350
- action();
23351
- }
23352
- }
23353
-
23354
- /**
23355
- * Moves focus to the first item or the last focused item if root is a toolbar.
23356
- *
23357
- * @method focusFirst
23358
- * @return {[type]} [description]
23359
- */
23360
- function focusFirst() {
23361
- var i, rootRole;
23362
-
23363
- rootRole = getRole(settings.root.getEl());
23364
- initItems();
23365
-
23366
- i = items.length;
23367
- while (i--) {
23368
- if (rootRole == 'toolbar' && items[i].id === focussedId) {
23369
- items[i].focus();
23370
- return;
23371
- }
23372
- }
23373
-
23374
- items[0].focus();
23375
- }
23376
-
23377
- // Handle accessible keys
23378
- root.on('keydown', function(e) {
23379
- var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
23380
- var DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32, DOM_VK_TAB = 9;
23381
- var preventDefault;
23382
-
23383
- switch (e.keyCode) {
23384
- case DOM_VK_LEFT:
23385
- if (enableLeftRight) {
23386
- if (settings.leftAction) {
23387
- settings.leftAction();
23388
- } else {
23389
- moveFocus(-1);
23390
- }
23391
-
23392
- preventDefault = true;
23393
- }
23394
- break;
23395
-
23396
- case DOM_VK_RIGHT:
23397
- if (enableLeftRight) {
23398
- if (getRole() == 'menuitem' && getParentRole() == 'menu') {
23399
- if (getAriaProp('haspopup')) {
23400
- action();
23401
- }
23402
- } else {
23403
- moveFocus(1);
23404
- }
23405
-
23406
- preventDefault = true;
23407
- }
23408
- break;
23409
-
23410
- case DOM_VK_UP:
23411
- if (enableUpDown) {
23412
- moveFocus(-1);
23413
- preventDefault = true;
23414
- }
23415
- break;
23416
-
23417
- case DOM_VK_DOWN:
23418
- if (enableUpDown) {
23419
- if (getRole() == 'menuitem' && getParentRole() == 'menubar') {
23420
- action();
23421
- } else if (getRole() == 'button' && getAriaProp('haspopup')) {
23422
- action();
23423
- } else {
23424
- moveFocus(1);
23425
- }
23426
-
23427
- preventDefault = true;
23428
- }
23429
- break;
23430
-
23431
- case DOM_VK_TAB:
23432
- preventDefault = true;
23433
-
23434
- if (e.shiftKey) {
23435
- moveFocus(-1);
23436
- } else {
23437
- moveFocus(1);
23438
- }
23439
- break;
23440
-
23441
- case DOM_VK_ESCAPE:
23442
- preventDefault = true;
23443
- cancel();
23444
- break;
23445
-
23446
- case DOM_VK_ENTER:
23447
- case DOM_VK_RETURN:
23448
- case DOM_VK_SPACE:
23449
- preventDefault = action();
23450
- break;
23451
- }
23452
-
23453
- if (preventDefault) {
23454
- e.stopPropagation();
23455
- e.preventDefault();
23456
- }
23457
- });
23458
-
23459
- // Init on focus in
23460
- root.on('focusin', function(e) {
23461
- initItems();
23462
- focussedId = e.target.id;
23463
- });
23464
-
23465
- return {
23466
- moveFocus: moveFocus,
23467
- focusFirst: focusFirst,
23468
- cancel: cancel
23469
- };
23470
- };
23471
- });
23472
-
23473
23617
  // Included from: js/tinymce/classes/ui/Window.js
23474
23618
 
23475
23619
  /**
@@ -23493,9 +23637,8 @@ define("tinymce/ui/Window", [
23493
23637
  "tinymce/ui/FloatPanel",
23494
23638
  "tinymce/ui/Panel",
23495
23639
  "tinymce/ui/DomUtils",
23496
- "tinymce/ui/KeyboardNavigation",
23497
23640
  "tinymce/ui/DragHelper"
23498
- ], function(FloatPanel, Panel, DomUtils, KeyboardNavigation, DragHelper) {
23641
+ ], function(FloatPanel, Panel, DomUtils, DragHelper) {
23499
23642
  "use strict";
23500
23643
 
23501
23644
  var Window = FloatPanel.extend({
@@ -23560,6 +23703,11 @@ define("tinymce/ui/Window", [
23560
23703
  }
23561
23704
  });
23562
23705
 
23706
+ self.on('cancel', function() {
23707
+ self.close();
23708
+ });
23709
+
23710
+ self.aria('describedby', self.describedBy || self._id + '-none');
23563
23711
  self.aria('label', settings.title);
23564
23712
  self._fullscreen = false;
23565
23713
  },
@@ -23667,7 +23815,7 @@ define("tinymce/ui/Window", [
23667
23815
  if (settings.title) {
23668
23816
  headerHtml = (
23669
23817
  '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
23670
- '<div class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
23818
+ '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
23671
23819
  '<button type="button" class="' + prefix + 'close" aria-hidden="true">&times;</button>' +
23672
23820
  '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
23673
23821
  '</div>'
@@ -23687,12 +23835,14 @@ define("tinymce/ui/Window", [
23687
23835
  }
23688
23836
 
23689
23837
  return (
23690
- '<div id="' + id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
23691
- headerHtml +
23692
- '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
23693
- html +
23838
+ '<div id="' + id + '" class="' + self.classes() + '" hideFocus="1">' +
23839
+ '<div class="' + self.classPrefix + 'reset" role="application">' +
23840
+ headerHtml +
23841
+ '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
23842
+ html +
23843
+ '</div>' +
23844
+ footerHtml +
23694
23845
  '</div>' +
23695
- footerHtml +
23696
23846
  '</div>'
23697
23847
  );
23698
23848
  },
@@ -23770,59 +23920,19 @@ define("tinymce/ui/Window", [
23770
23920
  * @method postRender
23771
23921
  */
23772
23922
  postRender: function() {
23773
- var self = this, items = [], focusCtrl, autoFocusFound, startPos;
23923
+ var self = this, startPos;
23774
23924
 
23775
23925
  setTimeout(function() {
23776
23926
  self.addClass('in');
23777
23927
  }, 0);
23778
23928
 
23779
- self.keyboardNavigation = new KeyboardNavigation({
23780
- root: self,
23781
- enableLeftRight: false,
23782
- enableUpDown: false,
23783
- items: items,
23784
- onCancel: function() {
23785
- self.close();
23786
- }
23787
- });
23788
-
23789
- self.find('*').each(function(ctrl) {
23790
- if (ctrl.canFocus) {
23791
- autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
23792
- focusCtrl = focusCtrl || ctrl;
23793
-
23794
- // TODO: Figure out a better way
23795
- if (ctrl.subinput) {
23796
- items.push(ctrl.getEl('inp'));
23797
-
23798
- if (ctrl.getEl('open')) {
23799
- items.push(ctrl.getEl('open'));
23800
- }
23801
- } else {
23802
- items.push(ctrl.getEl());
23803
- }
23804
- }
23805
- });
23806
-
23807
- if (self.statusbar) {
23808
- self.statusbar.find('*').each(function(ctrl) {
23809
- if (ctrl.canFocus) {
23810
- autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
23811
- focusCtrl = focusCtrl || ctrl;
23812
- items.push(ctrl.getEl());
23813
- }
23814
- });
23815
- }
23816
-
23817
23929
  self._super();
23818
23930
 
23819
23931
  if (self.statusbar) {
23820
23932
  self.statusbar.postRender();
23821
23933
  }
23822
23934
 
23823
- if (!autoFocusFound && focusCtrl) {
23824
- focusCtrl.focus();
23825
- }
23935
+ self.focus();
23826
23936
 
23827
23937
  this.dragHelper = new DragHelper(self._id + '-dragh', {
23828
23938
  start: function() {
@@ -24031,6 +24141,7 @@ define("tinymce/ui/MessageBox", [
24031
24141
  align: "center",
24032
24142
  buttons: buttons,
24033
24143
  title: settings.title,
24144
+ role: 'alertdialog',
24034
24145
  items: {
24035
24146
  type: "label",
24036
24147
  multiline: true,
@@ -24038,7 +24149,13 @@ define("tinymce/ui/MessageBox", [
24038
24149
  maxHeight: 200,
24039
24150
  text: settings.text
24040
24151
  },
24041
- onClose: settings.onClose
24152
+ onPostRender: function() {
24153
+ this.aria('describedby', this.items()[0]._id);
24154
+ },
24155
+ onClose: settings.onClose,
24156
+ onCancel: function() {
24157
+ callback(false);
24158
+ }
24042
24159
  }).renderTo(document.body).reflow();
24043
24160
  },
24044
24161
 
@@ -24145,7 +24262,7 @@ define("tinymce/WindowManager", [
24145
24262
  * @option {Number} height Height in pixels.
24146
24263
  * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
24147
24264
  * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
24148
- * @option {String/bool} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
24265
+ * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
24149
24266
  * larger than the popup size specified).
24150
24267
  */
24151
24268
  self.open = function(args, params) {
@@ -24238,6 +24355,8 @@ define("tinymce/WindowManager", [
24238
24355
  MessageBox.alert(message, function() {
24239
24356
  if (callback) {
24240
24357
  callback.call(scope || this);
24358
+ } else {
24359
+ editor.focus();
24241
24360
  }
24242
24361
  });
24243
24362
  };
@@ -24850,8 +24969,8 @@ define("tinymce/util/Quirks", [
24850
24969
  return;
24851
24970
  }
24852
24971
 
24853
- // Enable display: none in area and add a specific class that hides all BR elements in PRE to
24854
- // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
24972
+ // Enable display: none in area and add a specific class that hides all BR elements in PRE to
24973
+ // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
24855
24974
  setEditorCommandState('RespectVisibilityInDesign', true);
24856
24975
  editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
24857
24976
  dom.addClass(editor.getBody(), 'mceHideBrInPre');
@@ -25208,7 +25327,7 @@ define("tinymce/util/Quirks", [
25208
25327
  dom.bind(doc, 'mouseup', endSelection);
25209
25328
  dom.bind(doc, 'mousemove', selectionChange);
25210
25329
 
25211
- dom.win.focus();
25330
+ dom.getRoot().focus();
25212
25331
  startRng.select();
25213
25332
  }
25214
25333
  }
@@ -25272,8 +25391,8 @@ define("tinymce/util/Quirks", [
25272
25391
  editor.contentStyles.push('body {min-height: 150px}');
25273
25392
  editor.on('click', function(e) {
25274
25393
  if (e.target.nodeName == 'HTML') {
25275
- editor.execCommand('SelectAll');
25276
- editor.selection.collapse(true);
25394
+ editor.getBody().focus();
25395
+ editor.selection.normalize();
25277
25396
  editor.nodeChanged();
25278
25397
  }
25279
25398
  });
@@ -25302,6 +25421,21 @@ define("tinymce/util/Quirks", [
25302
25421
  setEditorCommandState("AutoUrlDetect", false);
25303
25422
  }
25304
25423
 
25424
+ /**
25425
+ * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
25426
+ * the iframe is hidden by display: none. This workaround solves this by switching
25427
+ * on designMode on the whole document.
25428
+ *
25429
+ * Example this: <body>text</body> becomes <body>text<br><br></body>
25430
+ */
25431
+ function doubleTrailingBrElements() {
25432
+ if (!editor.inline) {
25433
+ editor.on('init', function() {
25434
+ editor.getDoc().designMode = 'on';
25435
+ });
25436
+ }
25437
+ }
25438
+
25305
25439
  // All browsers
25306
25440
  disableBackspaceIntoATable();
25307
25441
  removeBlockQuoteOnBackSpace();
@@ -25339,6 +25473,7 @@ define("tinymce/util/Quirks", [
25339
25473
 
25340
25474
  if (Env.ie >= 11) {
25341
25475
  bodyHeight();
25476
+ doubleTrailingBrElements();
25342
25477
  }
25343
25478
 
25344
25479
  if (Env.ie) {
@@ -26984,7 +27119,7 @@ define("tinymce/Editor", [
26984
27119
  *
26985
27120
  * setup: function(ed) {
26986
27121
  * ed.addMenuItem('example', {
26987
- * title: 'My menu item',
27122
+ * text: 'My menu item',
26988
27123
  * context: 'tools',
26989
27124
  * onclick: function() {
26990
27125
  * ed.insertContent('Hello world!!');
@@ -27359,7 +27494,10 @@ define("tinymce/Editor", [
27359
27494
  html = args.content;
27360
27495
 
27361
27496
  if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
27362
- elm.innerHTML = html;
27497
+ // Update DIV element when not in inline mode
27498
+ if (!self.inline) {
27499
+ elm.innerHTML = html;
27500
+ }
27363
27501
 
27364
27502
  // Update hidden form element
27365
27503
  if ((form = DOM.getParent(self.id, 'form'))) {
@@ -27702,7 +27840,7 @@ define("tinymce/Editor", [
27702
27840
  case 'A':
27703
27841
  if (!dom.getAttrib(elm, 'href', false)) {
27704
27842
  value = dom.getAttrib(elm, 'name') || elm.id;
27705
- cls = 'mce-item-anchor';
27843
+ cls = settings.visual_anchor_class || 'mce-item-anchor';
27706
27844
 
27707
27845
  if (value) {
27708
27846
  if (self.hasVisual) {
@@ -27921,7 +28059,7 @@ define("tinymce/util/I18n", [], function() {
27921
28059
  * Property gets set to true if a RTL language pack was loaded.
27922
28060
  *
27923
28061
  * @property rtl
27924
- * @type {Boolean}
28062
+ * @type Boolean
27925
28063
  */
27926
28064
  rtl: false,
27927
28065
 
@@ -28069,14 +28207,24 @@ define("tinymce/FocusManager", [
28069
28207
  editor.on('init', function() {
28070
28208
  // On IE take selection snapshot onbeforedeactivate
28071
28209
  if ("onbeforedeactivate" in document && Env.ie < 11) {
28210
+ // Gets fired when the editor is about to be blurred but also when the selection
28211
+ // is moved into a table cell so we need to add the range as a pending range then
28212
+ // use that pending range on the blur event of the editor body
28072
28213
  editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
28073
28214
  try {
28074
- editor.lastRng = editor.selection.getRng();
28215
+ editor.pendingRng = editor.selection.getRng();
28075
28216
  } catch (ex) {
28076
28217
  // IE throws "Unexcpected call to method or property access" some times so lets ignore it
28077
28218
  }
28219
+ });
28078
28220
 
28079
- editor.selection.lastFocusBookmark = createBookmark(editor.lastRng);
28221
+ // Set the pending range as the current last range if the blur event occurs
28222
+ editor.dom.bind(editor.getBody(), 'blur', function() {
28223
+ if (editor.pendingRng) {
28224
+ editor.lastRng = editor.pendingRng;
28225
+ editor.selection.lastFocusBookmark = createBookmark(editor.lastRng);
28226
+ editor.pendingRng = null;
28227
+ }
28080
28228
  });
28081
28229
  } else if (editor.inline || Env.ie > 10) {
28082
28230
  // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
@@ -28142,7 +28290,7 @@ define("tinymce/FocusManager", [
28142
28290
  editorManager.activeEditor = editor;
28143
28291
  editorManager.focusedEditor = editor;
28144
28292
  editor.fire('focus', {blurredEditor: focusedEditor});
28145
- editor.focus(false);
28293
+ editor.focus(true);
28146
28294
  }
28147
28295
 
28148
28296
  editor.lastRng = null;
@@ -28172,7 +28320,6 @@ define("tinymce/FocusManager", [
28172
28320
  var activeEditor = editorManager.activeEditor;
28173
28321
 
28174
28322
  if (activeEditor && e.target.ownerDocument == document) {
28175
-
28176
28323
  // Check to make sure we have a valid selection
28177
28324
  if (activeEditor.selection) {
28178
28325
  activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.lastRng);
@@ -28254,7 +28401,7 @@ define("tinymce/EditorManager", [
28254
28401
  * @property minorVersion
28255
28402
  * @type String
28256
28403
  */
28257
- minorVersion : '0.16',
28404
+ minorVersion : '0.18',
28258
28405
 
28259
28406
  /**
28260
28407
  * Release date of TinyMCE build.
@@ -28262,7 +28409,7 @@ define("tinymce/EditorManager", [
28262
28409
  * @property releaseDate
28263
28410
  * @type String
28264
28411
  */
28265
- releaseDate: '2014-01-31',
28412
+ releaseDate: '2014-02-27',
28266
28413
 
28267
28414
  /**
28268
28415
  * Collection of editor instances.
@@ -29846,10 +29993,9 @@ define("tinymce/ui/Widget", [
29846
29993
  self.on('mouseleave mousedown click', function() {
29847
29994
  self.tooltip().hide();
29848
29995
  });
29849
-
29850
29996
  }
29851
29997
 
29852
- self.aria('label', settings.tooltip);
29998
+ self.aria('label', settings.ariaLabel || settings.tooltip);
29853
29999
  },
29854
30000
 
29855
30001
  /**
@@ -29859,11 +30005,9 @@ define("tinymce/ui/Widget", [
29859
30005
  * @return {tinymce.ui.Tooltip} Tooltip instance.
29860
30006
  */
29861
30007
  tooltip: function() {
29862
- var self = this;
29863
-
29864
30008
  if (!tooltip) {
29865
30009
  tooltip = new Tooltip({type: 'tooltip'});
29866
- tooltip.renderTo(self.getContainerElm());
30010
+ tooltip.renderTo();
29867
30011
  }
29868
30012
 
29869
30013
  return tooltip;
@@ -29923,9 +30067,7 @@ define("tinymce/ui/Widget", [
29923
30067
  }
29924
30068
 
29925
30069
  if (settings.autofocus) {
29926
- setTimeout(function() {
29927
- self.focus();
29928
- }, 0);
30070
+ self.focus();
29929
30071
  }
29930
30072
  },
29931
30073
 
@@ -30081,7 +30223,7 @@ define("tinymce/ui/Button", [
30081
30223
  icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
30082
30224
 
30083
30225
  return (
30084
- '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
30226
+ '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
30085
30227
  '<button role="presentation" type="button" tabindex="-1">' +
30086
30228
  (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
30087
30229
  (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
@@ -30130,7 +30272,7 @@ define("tinymce/ui/ButtonGroup", [
30130
30272
  return Container.extend({
30131
30273
  Defaults: {
30132
30274
  defaultType: 'button',
30133
- role: 'toolbar'
30275
+ role: 'group'
30134
30276
  },
30135
30277
 
30136
30278
  /**
@@ -30326,12 +30468,19 @@ define("tinymce/ui/PanelButton", [
30326
30468
  };
30327
30469
  }
30328
30470
 
30471
+ panelSettings.role = panelSettings.role || 'dialog';
30329
30472
  panelSettings.popover = true;
30330
30473
  panelSettings.autohide = true;
30474
+ panelSettings.ariaRoot = true;
30331
30475
 
30332
30476
  self.panel = new FloatPanel(panelSettings).on('hide', function() {
30333
30477
  self.active(false);
30478
+ }).on('cancel', function(e) {
30479
+ e.stopPropagation();
30480
+ self.focus();
30481
+ self.hidePanel();
30334
30482
  }).parent(self).renderTo(self.getContainerElm());
30483
+
30335
30484
  self.panel.fire('show');
30336
30485
  self.panel.reflow();
30337
30486
  } else {
@@ -30362,12 +30511,15 @@ define("tinymce/ui/PanelButton", [
30362
30511
  postRender: function() {
30363
30512
  var self = this;
30364
30513
 
30514
+ self.aria('haspopup', true);
30515
+
30365
30516
  self.on('click', function(e) {
30366
30517
  if (e.control === self) {
30367
30518
  if (self.panel && self.panel.visible()) {
30368
30519
  self.hidePanel();
30369
30520
  } else {
30370
30521
  self.showPanel();
30522
+ self.panel.focus(!!e.aria);
30371
30523
  }
30372
30524
  }
30373
30525
  });
@@ -30447,7 +30599,7 @@ define("tinymce/ui/ColorButton", [
30447
30599
  var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
30448
30600
 
30449
30601
  return (
30450
- '<div id="' + id + '" class="' + self.classes() + '">' +
30602
+ '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
30451
30603
  '<button role="presentation" hidefocus type="button" tabindex="-1">' +
30452
30604
  (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
30453
30605
  '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
@@ -30469,6 +30621,10 @@ define("tinymce/ui/ColorButton", [
30469
30621
  var self = this, onClickHandler = self.settings.onclick;
30470
30622
 
30471
30623
  self.on('click', function(e) {
30624
+ if (e.aria && e.aria.key == 'down') {
30625
+ return;
30626
+ }
30627
+
30472
30628
  if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
30473
30629
  e.stopImmediatePropagation();
30474
30630
  onClickHandler.call(self, e);
@@ -30524,6 +30680,7 @@ define("tinymce/ui/ComboBox", [
30524
30680
  self._super(settings);
30525
30681
  self.addClass('combobox');
30526
30682
  self.subinput = true;
30683
+ self.ariaTarget = 'inp'; // TODO: Figure out a better way
30527
30684
 
30528
30685
  settings = self.settings;
30529
30686
  settings.menu = settings.menu || settings.values;
@@ -30533,16 +30690,16 @@ define("tinymce/ui/ComboBox", [
30533
30690
  }
30534
30691
 
30535
30692
  self.on('click', function(e) {
30536
- var elm = e.target;
30693
+ var elm = e.target, root = self.getEl();
30537
30694
 
30538
- while (elm) {
30695
+ while (elm && elm != root) {
30539
30696
  if (elm.id && elm.id.indexOf('-open') != -1) {
30540
30697
  self.fire('action');
30541
30698
 
30542
30699
  if (settings.menu) {
30543
30700
  self.showMenu();
30544
30701
 
30545
- if (e.keyboard) {
30702
+ if (e.aria) {
30546
30703
  self.menu.items()[0].focus();
30547
30704
  }
30548
30705
  }
@@ -30795,7 +30952,7 @@ define("tinymce/ui/ComboBox", [
30795
30952
 
30796
30953
  if (icon || text) {
30797
30954
  openBtnHtml = (
30798
- '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1">' +
30955
+ '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
30799
30956
  '<button id="' + id + '-action" type="button" hidefocus tabindex="-1">' +
30800
30957
  (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
30801
30958
  (text ? (icon ? ' ' : '') + text : '') +
@@ -30837,9 +30994,8 @@ define("tinymce/ui/ComboBox", [
30837
30994
  * @extends tinymce.ui.Widget
30838
30995
  */
30839
30996
  define("tinymce/ui/Path", [
30840
- "tinymce/ui/Widget",
30841
- "tinymce/ui/KeyboardNavigation"
30842
- ], function(Widget, KeyboardNavigation) {
30997
+ "tinymce/ui/Widget"
30998
+ ], function(Widget) {
30843
30999
  "use strict";
30844
31000
 
30845
31001
  return Widget.extend({
@@ -30879,12 +31035,7 @@ define("tinymce/ui/Path", [
30879
31035
  focus: function() {
30880
31036
  var self = this;
30881
31037
 
30882
- self.keyNav = new KeyboardNavigation({
30883
- root: self,
30884
- enableLeftRight: true
30885
- });
30886
-
30887
- self.keyNav.focusFirst();
31038
+ self.getEl().firstChild.focus();
30888
31039
 
30889
31040
  return self;
30890
31041
  },
@@ -30953,7 +31104,7 @@ define("tinymce/ui/Path", [
30953
31104
  html += (
30954
31105
  (i > 0 ? '<div class="'+ prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
30955
31106
  '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
30956
- i + '" tabindex="-1" id="' + self._id + '-' + i +'">' + parts[i].name + '</div>'
31107
+ i + '" tabindex="-1" id="' + self._id + '-' + i +'" aria-level="' + i + '">' + parts[i].name + '</div>'
30957
31108
  );
30958
31109
  }
30959
31110
 
@@ -31181,11 +31332,12 @@ define("tinymce/ui/Form", [
31181
31332
  autoResize: "overflow",
31182
31333
  defaults: {flex: 1},
31183
31334
  items: [
31184
- {type: 'label', text: label, flex: 0, forId: ctrl._id, disabled: ctrl.disabled()}
31335
+ {type: 'label', id: ctrl._id + '-l', text: label, flex: 0, forId: ctrl._id, disabled: ctrl.disabled()}
31185
31336
  ]
31186
31337
  });
31187
31338
 
31188
31339
  formItem.type = 'formitem';
31340
+ ctrl.aria('labelledby', ctrl._id + '-l');
31189
31341
 
31190
31342
  if (typeof(ctrl.settings.flex) == "undefined") {
31191
31343
  ctrl.settings.flex = 1;
@@ -32852,9 +33004,8 @@ define("tinymce/ui/Label", [
32852
33004
  * @extends tinymce.ui.Container
32853
33005
  */
32854
33006
  define("tinymce/ui/Toolbar", [
32855
- "tinymce/ui/Container",
32856
- "tinymce/ui/KeyboardNavigation"
32857
- ], function(Container, KeyboardNavigation) {
33007
+ "tinymce/ui/Container"
33008
+ ], function(Container) {
32858
33009
  "use strict";
32859
33010
 
32860
33011
  return Container.extend({
@@ -32886,11 +33037,6 @@ define("tinymce/ui/Toolbar", [
32886
33037
 
32887
33038
  self.items().addClass('toolbar-item');
32888
33039
 
32889
- self.keyNav = new KeyboardNavigation({
32890
- root: self,
32891
- enableLeftRight: true
32892
- });
32893
-
32894
33040
  return self._super();
32895
33041
  }
32896
33042
  });
@@ -32924,6 +33070,7 @@ define("tinymce/ui/MenuBar", [
32924
33070
  Defaults: {
32925
33071
  role: 'menubar',
32926
33072
  containerCls: 'menubar',
33073
+ ariaRoot: true,
32927
33074
  defaults: {
32928
33075
  type: 'menubutton'
32929
33076
  }
@@ -33018,22 +33165,29 @@ define("tinymce/ui/MenuButton", [
33018
33165
  menu.type = menu.type || 'menu';
33019
33166
  }
33020
33167
 
33021
- self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
33168
+ self.menu = Factory.create(menu).parent(self).renderTo();
33022
33169
  self.fire('createmenu');
33023
33170
  self.menu.reflow();
33024
33171
  self.menu.on('cancel', function(e) {
33025
- if (e.control === self.menu) {
33172
+ if (e.control.parent() === self.menu) {
33173
+ e.stopPropagation();
33026
33174
  self.focus();
33175
+ self.hideMenu();
33027
33176
  }
33028
33177
  });
33029
33178
 
33179
+ // Move focus to button when a menu item is selected/clicked
33180
+ self.menu.on('select', function() {
33181
+ self.focus();
33182
+ });
33183
+
33030
33184
  self.menu.on('show hide', function(e) {
33031
33185
  if (e.control == self.menu) {
33032
33186
  self.activeMenu(e.type == 'show');
33033
33187
  }
33034
- }).fire('show');
33035
33188
 
33036
- self.aria('expanded', true);
33189
+ self.aria('expanded', e.type == 'show');
33190
+ }).fire('show');
33037
33191
  }
33038
33192
 
33039
33193
  self.menu.show();
@@ -33057,7 +33211,6 @@ define("tinymce/ui/MenuButton", [
33057
33211
  });
33058
33212
 
33059
33213
  self.menu.hide();
33060
- self.aria('expanded', false);
33061
33214
  }
33062
33215
  },
33063
33216
 
@@ -33083,7 +33236,7 @@ define("tinymce/ui/MenuButton", [
33083
33236
  self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
33084
33237
 
33085
33238
  return (
33086
- '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
33239
+ '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
33087
33240
  '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
33088
33241
  (icon ? '<i class="' + icon + '"></i>' : '') +
33089
33242
  '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
@@ -33105,7 +33258,7 @@ define("tinymce/ui/MenuButton", [
33105
33258
  if (e.control === self && isChildOf(e.target, self.getEl())) {
33106
33259
  self.showMenu();
33107
33260
 
33108
- if (e.keyboard) {
33261
+ if (e.aria) {
33109
33262
  self.menu.items()[0].focus();
33110
33263
  }
33111
33264
  }
@@ -33215,9 +33368,16 @@ define("tinymce/ui/ListBox", [
33215
33368
  if (selected) {
33216
33369
  selectedText = selectedText || values[i].text;
33217
33370
  self._value = values[i].value;
33371
+ break;
33218
33372
  }
33219
33373
  }
33220
33374
 
33375
+ // Default with first item
33376
+ if (!selected && values.length > 0) {
33377
+ selectedText = values[0].text;
33378
+ self._value = values[0].value;
33379
+ }
33380
+
33221
33381
  settings.menu = values;
33222
33382
  }
33223
33383
 
@@ -33356,13 +33516,11 @@ define("tinymce/ui/MenuItem", [
33356
33516
  if (self._text === '-' || self._text === '|') {
33357
33517
  self.addClass('menu-item-sep');
33358
33518
  self.aria('role', 'separator');
33359
- self.canFocus = false;
33360
33519
  self._text = '-';
33361
33520
  }
33362
33521
 
33363
33522
  if (settings.selectable) {
33364
33523
  self.aria('role', 'menuitemcheckbox');
33365
- self.aria('checked', true);
33366
33524
  self.addClass('menu-item-checkbox');
33367
33525
  settings.icon = 'selected';
33368
33526
  }
@@ -33375,24 +33533,6 @@ define("tinymce/ui/MenuItem", [
33375
33533
  e.preventDefault();
33376
33534
  });
33377
33535
 
33378
- self.on('mouseenter click', function(e) {
33379
- if (e.control === self) {
33380
- if (!settings.menu && e.type === 'click') {
33381
- self.parent().hideAll();
33382
- self.fire('cancel');
33383
- self.fire('select');
33384
- } else {
33385
- self.showMenu();
33386
-
33387
- if (e.keyboard) {
33388
- setTimeout(function() {
33389
- self.menu.items()[0].focus();
33390
- }, 0);
33391
- }
33392
- }
33393
- }
33394
- });
33395
-
33396
33536
  if (settings.menu) {
33397
33537
  self.aria('haspopup', true);
33398
33538
  }
@@ -33442,11 +33582,13 @@ define("tinymce/ui/MenuItem", [
33442
33582
  menu.itemDefaults = parent.settings.itemDefaults;
33443
33583
  }
33444
33584
 
33445
- menu = self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
33585
+ menu = self.menu = Factory.create(menu).parent(self).renderTo();
33446
33586
  menu.reflow();
33447
33587
  menu.fire('show');
33448
- menu.on('cancel', function() {
33588
+ menu.on('cancel', function(e) {
33589
+ e.stopPropagation();
33449
33590
  self.focus();
33591
+ menu.hide();
33450
33592
  });
33451
33593
 
33452
33594
  menu.on('hide', function(e) {
@@ -33454,6 +33596,8 @@ define("tinymce/ui/MenuItem", [
33454
33596
  self.removeClass('selected');
33455
33597
  }
33456
33598
  });
33599
+
33600
+ menu.submenu = true;
33457
33601
  } else {
33458
33602
  menu.show();
33459
33603
  }
@@ -33535,8 +33679,7 @@ define("tinymce/ui/MenuItem", [
33535
33679
  '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
33536
33680
  (text !== '-' ? '<i class="' + icon + '"' + image + '></i>&nbsp;' : '') +
33537
33681
  (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
33538
- (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' +
33539
- shortcut + '</div>' : '') +
33682
+ (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
33540
33683
  (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
33541
33684
  '</div>'
33542
33685
  );
@@ -33562,7 +33705,32 @@ define("tinymce/ui/MenuItem", [
33562
33705
  }
33563
33706
  }
33564
33707
 
33565
- return self._super();
33708
+ self.on('mouseenter click', function(e) {
33709
+ if (e.control === self) {
33710
+ if (!settings.menu && e.type === 'click') {
33711
+ self.fire('select');
33712
+ self.parent().hideAll();
33713
+ } else {
33714
+ self.showMenu();
33715
+
33716
+ if (e.aria) {
33717
+ self.menu.focus(true);
33718
+ }
33719
+ }
33720
+ }
33721
+ });
33722
+
33723
+ self._super();
33724
+
33725
+ return self;
33726
+ },
33727
+
33728
+ active: function(state) {
33729
+ if (typeof(state) != "undefined") {
33730
+ this.aria('checked', state);
33731
+ }
33732
+
33733
+ return this._super(state);
33566
33734
  },
33567
33735
 
33568
33736
  /**
@@ -33601,10 +33769,9 @@ define("tinymce/ui/MenuItem", [
33601
33769
  */
33602
33770
  define("tinymce/ui/Menu", [
33603
33771
  "tinymce/ui/FloatPanel",
33604
- "tinymce/ui/KeyboardNavigation",
33605
33772
  "tinymce/ui/MenuItem",
33606
33773
  "tinymce/util/Tools"
33607
- ], function(FloatPanel, KeyboardNavigation, MenuItem, Tools) {
33774
+ ], function(FloatPanel, MenuItem, Tools) {
33608
33775
  "use strict";
33609
33776
 
33610
33777
  var Menu = FloatPanel.extend({
@@ -33612,7 +33779,9 @@ define("tinymce/ui/Menu", [
33612
33779
  defaultType: 'menuitem',
33613
33780
  border: 1,
33614
33781
  layout: 'stack',
33615
- role: 'menu'
33782
+ role: 'application',
33783
+ bodyRole: 'menu',
33784
+ ariaRoot: true
33616
33785
  },
33617
33786
 
33618
33787
  /**
@@ -33637,23 +33806,6 @@ define("tinymce/ui/Menu", [
33637
33806
 
33638
33807
  self._super(settings);
33639
33808
  self.addClass('menu');
33640
-
33641
- self.keyNav = new KeyboardNavigation({
33642
- root: self,
33643
- enableUpDown: true,
33644
- enableLeftRight: true,
33645
-
33646
- leftAction: function() {
33647
- if (self.parent() instanceof MenuItem) {
33648
- self.keyNav.cancel();
33649
- }
33650
- },
33651
-
33652
- onCancel: function() {
33653
- self.fire('cancel', {}, false);
33654
- self.hide();
33655
- }
33656
- });
33657
33809
  },
33658
33810
 
33659
33811
  /**
@@ -33681,7 +33833,6 @@ define("tinymce/ui/Menu", [
33681
33833
  var self = this;
33682
33834
 
33683
33835
  self.hideAll();
33684
- self.fire('cancel');
33685
33836
  self.fire('select');
33686
33837
  },
33687
33838
 
@@ -33697,7 +33848,25 @@ define("tinymce/ui/Menu", [
33697
33848
 
33698
33849
  return self._super();
33699
33850
  },
33851
+ /*
33852
+ getContainerElm: function() {
33853
+ var doc = document, id = this.classPrefix + 'menucontainer';
33700
33854
 
33855
+ var elm = doc.getElementById(id);
33856
+ if (!elm) {
33857
+ elm = doc.createElement('div');
33858
+ elm.id = id;
33859
+ elm.setAttribute('role', 'application');
33860
+ elm.className = this.classPrefix + '-reset';
33861
+ elm.style.position = 'absolute';
33862
+ elm.style.top = elm.style.left = '0';
33863
+ elm.style.overflow = 'visible';
33864
+ doc.body.appendChild(elm);
33865
+ }
33866
+
33867
+ return elm;
33868
+ },
33869
+ */
33701
33870
  /**
33702
33871
  * Invoked before the menu is rendered.
33703
33872
  *
@@ -33911,7 +34080,7 @@ define("tinymce/ui/SplitButton", [
33911
34080
  return MenuButton.extend({
33912
34081
  Defaults: {
33913
34082
  classes: "widget btn splitbtn",
33914
- role: "splitbutton"
34083
+ role: "button"
33915
34084
  },
33916
34085
 
33917
34086
  /**
@@ -33961,7 +34130,7 @@ define("tinymce/ui/SplitButton", [
33961
34130
  var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
33962
34131
 
33963
34132
  return (
33964
- '<div id="' + id + '" class="' + self.classes() + '">' +
34133
+ '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
33965
34134
  '<button type="button" hidefocus tabindex="-1">' +
33966
34135
  (icon ? '<i class="' + icon + '"></i>' : '') +
33967
34136
  (self._text ? (icon ? ' ' : '') + self._text : '') +
@@ -33989,7 +34158,7 @@ define("tinymce/ui/SplitButton", [
33989
34158
  if (e.control == this) {
33990
34159
  // Find clicks that is on the main button
33991
34160
  while (node) {
33992
- if (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1) {
34161
+ if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
33993
34162
  e.stopImmediatePropagation();
33994
34163
  onClickHandler.call(this, e);
33995
34164
  return;
@@ -34084,13 +34253,19 @@ define("tinymce/ui/TabPanel", [
34084
34253
  * @param {Number} idx Index of the tab to activate.
34085
34254
  */
34086
34255
  activateTab: function(idx) {
34256
+ var activeTabElm;
34257
+
34087
34258
  if (this.activeTabId) {
34088
- DomUtils.removeClass(this.getEl(this.activeTabId), this.classPrefix + 'active');
34259
+ activeTabElm = this.getEl(this.activeTabId);
34260
+ DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
34261
+ activeTabElm.setAttribute('aria-selected', "false");
34089
34262
  }
34090
34263
 
34091
34264
  this.activeTabId = 't' + idx;
34092
34265
 
34093
- DomUtils.addClass(this.getEl('t' + idx), this.classPrefix + 'active');
34266
+ activeTabElm = this.getEl('t' + idx);
34267
+ activeTabElm.setAttribute('aria-selected', "true");
34268
+ DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
34094
34269
 
34095
34270
  if (idx != this.lastIdx) {
34096
34271
  this.items()[this.lastIdx].hide();
@@ -34114,8 +34289,14 @@ define("tinymce/ui/TabPanel", [
34114
34289
  layout.preRender(self);
34115
34290
 
34116
34291
  self.items().each(function(ctrl, i) {
34292
+ var id = self._id + '-t' + i;
34293
+
34294
+ ctrl.aria('role', 'tabpanel');
34295
+ ctrl.aria('labelledby', id);
34296
+
34117
34297
  tabsHtml += (
34118
- '<div id="' + self._id + '-t' + i + '" class="' + prefix + 'tab" unselectable="on">' +
34298
+ '<div id="' + id + '" class="' + prefix + 'tab" '+
34299
+ 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
34119
34300
  self.encode(ctrl.settings.title) +
34120
34301
  '</div>'
34121
34302
  );
@@ -34123,7 +34304,7 @@ define("tinymce/ui/TabPanel", [
34123
34304
 
34124
34305
  return (
34125
34306
  '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
34126
- '<div id="' + self._id + '-head" class="' + prefix + 'tabs">' +
34307
+ '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
34127
34308
  tabsHtml +
34128
34309
  '</div>' +
34129
34310
  '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
@@ -34501,5 +34682,5 @@ define("tinymce/ui/Throbber", [
34501
34682
  };
34502
34683
  });
34503
34684
 
34504
- expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/Selection","tinymce/dom/RangeUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/KeyboardNavigation","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","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"]);
34685
+ expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/RangeUtils","tinymce/dom/Selection","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","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/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"]);
34505
34686
  })(this);