tinymce-rails 4.3.7 → 4.3.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c9f2676e96cc3a5dad5574f8ab53d712cf89871
4
- data.tar.gz: adac5380dc67f0f9460a8b871e80dac9bb77da4a
3
+ metadata.gz: 93901d87a045935cc28a6792994235e6128db919
4
+ data.tar.gz: f5adf4ad22f9bd5fca01a2137d62bc40055e2245
5
5
  SHA512:
6
- metadata.gz: 3d941436b663ba7487505ebf2c6e65dac9123c223e6ba7636d87199b719394c8338704b10193a67a110077c255b6b53b2e14d2d0512838ca4f420abcafb81387
7
- data.tar.gz: 0ec890ac6b33b76bd862cf061dde2659897bdc0f1263d849be630d90a82f2583151c9fcb2f9021c65db288555ae9d0c65ba778b315333e2012a1ac841dc528a0
6
+ metadata.gz: 5add811679fa66c7f4002b391ded25baa216ff442f661cabc0e1364d08160c129d1a2f48be30571b9260b8168213ecac9fb8fb5aba130f186fac9e77a2a93a25
7
+ data.tar.gz: 917fb5ba654b40f5e641b4bf01113f3ae1902b5b0a2fac8180be40415524a4348009c5f4b27756e4634fe1f8b7fce2c8796e128ff807d264ccc65594a3bc3208
@@ -1,4 +1,4 @@
1
- // 4.3.7 (2016-03-02)
1
+ // 4.3.8 (2016-03-15)
2
2
 
3
3
  /**
4
4
  * Compiled inline version. (Library mode)
@@ -12121,8 +12121,8 @@ define("tinymce/html/Schema", [
12121
12121
  });
12122
12122
  }
12123
12123
 
12124
- // Add default alt attribute for images
12125
- elements.img.attributesDefault = [{name: 'alt', value: ''}];
12124
+ // Add default alt attribute for images, removed since alt="" is treated as presentational.
12125
+ // elements.img.attributesDefault = [{name: 'alt', value: ''}];
12126
12126
 
12127
12127
  // Remove these if they are empty by default
12128
12128
  each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
@@ -15211,6 +15211,7 @@ define("tinymce/dom/ControlSelection", [
15211
15211
  rootClass + ' div.mce-resizehandle {' +
15212
15212
  'position: absolute;' +
15213
15213
  'border: 1px solid black;' +
15214
+ 'box-sizing: box-sizing;' +
15214
15215
  'background: #FFF;' +
15215
15216
  'width: 7px;' +
15216
15217
  'height: 7px;' +
@@ -18728,12 +18729,26 @@ define("tinymce/Formatter", [
18728
18729
 
18729
18730
  alignleft: [
18730
18731
  {selector: 'figure.image', collapsed: false, classes: 'align-left', ceFalseOverride: true},
18731
- {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
18732
+ {
18733
+ selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
18734
+ styles: {
18735
+ textAlign: 'left'
18736
+ },
18737
+ inherit: false,
18738
+ defaultBlock: 'div'
18739
+ },
18732
18740
  {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
18733
18741
  ],
18734
18742
 
18735
18743
  aligncenter: [
18736
- {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
18744
+ {
18745
+ selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
18746
+ styles: {
18747
+ textAlign: 'center'
18748
+ },
18749
+ inherit: false,
18750
+ defaultBlock: 'div'
18751
+ },
18737
18752
  {selector: 'figure.image', collapsed: false, classes: 'align-center', ceFalseOverride: true},
18738
18753
  {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
18739
18754
  {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
@@ -18741,12 +18756,26 @@ define("tinymce/Formatter", [
18741
18756
 
18742
18757
  alignright: [
18743
18758
  {selector: 'figure.image', collapsed: false, classes: 'align-right', ceFalseOverride: true},
18744
- {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
18759
+ {
18760
+ selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
18761
+ styles: {
18762
+ textAlign: 'right'
18763
+ },
18764
+ inherit: false,
18765
+ defaultBlock: 'div'
18766
+ },
18745
18767
  {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
18746
18768
  ],
18747
18769
 
18748
18770
  alignjustify: [
18749
- {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
18771
+ {
18772
+ selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
18773
+ styles: {
18774
+ textAlign: 'justify'
18775
+ },
18776
+ inherit: false,
18777
+ defaultBlock: 'div'
18778
+ }
18750
18779
  ],
18751
18780
 
18752
18781
  bold: [
@@ -18911,6 +18940,20 @@ define("tinymce/Formatter", [
18911
18940
  return formats;
18912
18941
  }
18913
18942
 
18943
+ function matchesUnInheritedFormatSelector(node, name) {
18944
+ var formatList = get(name);
18945
+
18946
+ if (formatList) {
18947
+ for (var i = 0; i < formatList.length; i++) {
18948
+ if (formatList[i].inherit === false && dom.is(node, formatList[i].selector)) {
18949
+ return true;
18950
+ }
18951
+ }
18952
+ }
18953
+
18954
+ return false;
18955
+ }
18956
+
18914
18957
  function getTextDecoration(node) {
18915
18958
  var decoration;
18916
18959
 
@@ -18981,10 +19024,15 @@ define("tinymce/Formatter", [
18981
19024
  }
18982
19025
  }
18983
19026
 
19027
+ // This converts: <p>[a</p><p>]b</p> -> <p>[a]</p><p>b</p>
18984
19028
  function adjustSelectionToVisibleSelection() {
18985
19029
  function findSelectionEnd(start, end) {
18986
19030
  var walker = new TreeWalker(end);
18987
- for (node = walker.current(); node; node = walker.prev()) {
19031
+ for (node = walker.prev2(); node; node = walker.prev2()) {
19032
+ if (node.nodeType == 3 && node.data.length > 0) {
19033
+ return node;
19034
+ }
19035
+
18988
19036
  if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
18989
19037
  return node;
18990
19038
  }
@@ -18999,7 +19047,7 @@ define("tinymce/Formatter", [
18999
19047
 
19000
19048
  if (start != end && rng.endOffset === 0) {
19001
19049
  var newEnd = findSelectionEnd(start, end);
19002
- var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
19050
+ var endOffset = newEnd.nodeType == 3 ? newEnd.data.length : newEnd.childNodes.length;
19003
19051
 
19004
19052
  rng.setEnd(newEnd, endOffset);
19005
19053
  }
@@ -19669,6 +19717,10 @@ define("tinymce/Formatter", [
19669
19717
 
19670
19718
  // Find first node with similar format settings
19671
19719
  node = dom.getParent(node, function(node) {
19720
+ if (matchesUnInheritedFormatSelector(node, name)) {
19721
+ return true;
19722
+ }
19723
+
19672
19724
  return node.parentNode === root || !!matchNode(node, name, vars, true);
19673
19725
  });
19674
19726
 
@@ -19802,6 +19854,10 @@ define("tinymce/Formatter", [
19802
19854
  matchedFormats[format] = callbacks;
19803
19855
  return false;
19804
19856
  }
19857
+
19858
+ if (matchesUnInheritedFormatSelector(node, format)) {
19859
+ return false;
19860
+ }
19805
19861
  });
19806
19862
  });
19807
19863
 
@@ -22172,10 +22228,10 @@ define("tinymce/ForceBlocks", [], function() {
22172
22228
  };
22173
22229
  });
22174
22230
 
22175
- // Included from: js/tinymce/classes/EditorCommands.js
22231
+ // Included from: js/tinymce/classes/caret/CaretUtils.js
22176
22232
 
22177
22233
  /**
22178
- * EditorCommands.js
22234
+ * CaretUtils.js
22179
22235
  *
22180
22236
  * Released under LGPL License.
22181
22237
  * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -22185,371 +22241,897 @@ define("tinymce/ForceBlocks", [], function() {
22185
22241
  */
22186
22242
 
22187
22243
  /**
22188
- * This class enables you to add custom editor commands and it contains
22189
- * overrides for native browser commands to address various bugs and issues.
22244
+ * Utility functions shared by the caret logic.
22190
22245
  *
22191
- * @class tinymce.EditorCommands
22246
+ * @private
22247
+ * @class tinymce.caret.CaretUtils
22192
22248
  */
22193
- define("tinymce/EditorCommands", [
22194
- "tinymce/html/Serializer",
22195
- "tinymce/Env",
22196
- "tinymce/util/Tools",
22197
- "tinymce/dom/ElementUtils",
22198
- "tinymce/dom/RangeUtils",
22199
- "tinymce/dom/TreeWalker"
22200
- ], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
22201
- // Added for compression purposes
22202
- var each = Tools.each, extend = Tools.extend;
22203
- var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
22204
- var isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
22205
- var TRUE = true, FALSE = false;
22206
-
22207
- return function(editor) {
22208
- var dom, selection, formatter,
22209
- commands = {state: {}, exec: {}, value: {}},
22210
- settings = editor.settings,
22211
- bookmark;
22212
-
22213
- editor.on('PreInit', function() {
22214
- dom = editor.dom;
22215
- selection = editor.selection;
22216
- settings = editor.settings;
22217
- formatter = editor.formatter;
22218
- });
22219
-
22220
- /**
22221
- * Executes the specified command.
22222
- *
22223
- * @method execCommand
22224
- * @param {String} command Command to execute.
22225
- * @param {Boolean} ui Optional user interface state.
22226
- * @param {Object} value Optional value for command.
22227
- * @param {Object} args Optional extra arguments to the execCommand.
22228
- * @return {Boolean} true/false if the command was found or not.
22229
- */
22230
- function execCommand(command, ui, value, args) {
22231
- var func, customCommand, state = 0;
22249
+ define("tinymce/caret/CaretUtils", [
22250
+ "tinymce/util/Fun",
22251
+ "tinymce/dom/TreeWalker",
22252
+ "tinymce/dom/NodeType",
22253
+ "tinymce/caret/CaretPosition",
22254
+ "tinymce/caret/CaretContainer",
22255
+ "tinymce/caret/CaretCandidate"
22256
+ ], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) {
22257
+ var isContentEditableTrue = NodeType.isContentEditableTrue,
22258
+ isContentEditableFalse = NodeType.isContentEditableFalse,
22259
+ isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'),
22260
+ isCaretContainer = CaretContainer.isCaretContainer,
22261
+ curry = Fun.curry,
22262
+ isElement = NodeType.isElement,
22263
+ isCaretCandidate = CaretCandidate.isCaretCandidate;
22232
22264
 
22233
- if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
22234
- editor.focus();
22235
- }
22265
+ function isForwards(direction) {
22266
+ return direction > 0;
22267
+ }
22236
22268
 
22237
- args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
22238
- if (args.isDefaultPrevented()) {
22239
- return false;
22240
- }
22269
+ function isBackwards(direction) {
22270
+ return direction < 0;
22271
+ }
22241
22272
 
22242
- customCommand = command.toLowerCase();
22243
- if ((func = commands.exec[customCommand])) {
22244
- func(customCommand, ui, value);
22245
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22246
- return true;
22247
- }
22273
+ function findNode(node, direction, predicateFn, rootNode, shallow) {
22274
+ var walker = new TreeWalker(node, rootNode);
22248
22275
 
22249
- // Plugin commands
22250
- each(editor.plugins, function(p) {
22251
- if (p.execCommand && p.execCommand(command, ui, value)) {
22252
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22253
- state = true;
22254
- return false;
22276
+ if (isBackwards(direction)) {
22277
+ if (isContentEditableFalse(node)) {
22278
+ node = walker.prev(true);
22279
+ if (predicateFn(node)) {
22280
+ return node;
22255
22281
  }
22256
- });
22257
-
22258
- if (state) {
22259
- return state;
22260
22282
  }
22261
22283
 
22262
- // Theme commands
22263
- if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
22264
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22265
- return true;
22284
+ while ((node = walker.prev(shallow))) {
22285
+ if (predicateFn(node)) {
22286
+ return node;
22287
+ }
22266
22288
  }
22289
+ }
22267
22290
 
22268
- // Browser commands
22269
- try {
22270
- state = editor.getDoc().execCommand(command, ui, value);
22271
- } catch (ex) {
22272
- // Ignore old IE errors
22291
+ if (isForwards(direction)) {
22292
+ if (isContentEditableFalse(node)) {
22293
+ node = walker.next(true);
22294
+ if (predicateFn(node)) {
22295
+ return node;
22296
+ }
22273
22297
  }
22274
22298
 
22275
- if (state) {
22276
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22277
- return true;
22299
+ while ((node = walker.next(shallow))) {
22300
+ if (predicateFn(node)) {
22301
+ return node;
22302
+ }
22278
22303
  }
22279
-
22280
- return false;
22281
22304
  }
22282
22305
 
22283
- /**
22284
- * Queries the current state for a command for example if the current selection is "bold".
22285
- *
22286
- * @method queryCommandState
22287
- * @param {String} command Command to check the state of.
22288
- * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
22289
- */
22290
- function queryCommandState(command) {
22291
- var func;
22306
+ return null;
22307
+ }
22292
22308
 
22293
- // Is hidden then return undefined
22294
- if (editor._isHidden()) {
22295
- return;
22309
+ function getEditingHost(node, rootNode) {
22310
+ for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
22311
+ if (isContentEditableTrue(node)) {
22312
+ return node;
22296
22313
  }
22314
+ }
22297
22315
 
22298
- command = command.toLowerCase();
22299
- if ((func = commands.state[command])) {
22300
- return func(command);
22301
- }
22316
+ return rootNode;
22317
+ }
22302
22318
 
22303
- // Browser commands
22304
- try {
22305
- return editor.getDoc().queryCommandState(command);
22306
- } catch (ex) {
22307
- // Fails sometimes see bug: 1896577
22319
+ function getParentBlock(node, rootNode) {
22320
+ while (node && node != rootNode) {
22321
+ if (isBlockLike(node)) {
22322
+ return node;
22308
22323
  }
22309
22324
 
22310
- return false;
22325
+ node = node.parentNode;
22311
22326
  }
22312
22327
 
22313
- /**
22314
- * Queries the command value for example the current fontsize.
22315
- *
22316
- * @method queryCommandValue
22317
- * @param {String} command Command to check the value of.
22318
- * @return {Object} Command value of false if it's not found.
22319
- */
22320
- function queryCommandValue(command) {
22321
- var func;
22328
+ return null;
22329
+ }
22322
22330
 
22323
- // Is hidden then return undefined
22324
- if (editor._isHidden()) {
22325
- return;
22326
- }
22331
+ function isInSameBlock(caretPosition1, caretPosition2, rootNode) {
22332
+ return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode);
22333
+ }
22327
22334
 
22328
- command = command.toLowerCase();
22329
- if ((func = commands.value[command])) {
22330
- return func(command);
22331
- }
22335
+ function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) {
22336
+ return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode);
22337
+ }
22332
22338
 
22333
- // Browser commands
22334
- try {
22335
- return editor.getDoc().queryCommandValue(command);
22336
- } catch (ex) {
22337
- // Fails sometimes see bug: 1896577
22338
- }
22339
+ function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) {
22340
+ var container, offset;
22341
+
22342
+ if (!caretPosition) {
22343
+ return null;
22339
22344
  }
22340
22345
 
22341
- /**
22342
- * Adds commands to the command collection.
22343
- *
22344
- * @method addCommands
22345
- * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
22346
- * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
22347
- */
22348
- function addCommands(command_list, type) {
22349
- type = type || 'exec';
22346
+ container = caretPosition.container();
22347
+ offset = caretPosition.offset();
22350
22348
 
22351
- each(command_list, function(callback, command) {
22352
- each(command.toLowerCase().split(','), function(command) {
22353
- commands[type][command] = callback;
22354
- });
22355
- });
22349
+ if (!isElement(container)) {
22350
+ return null;
22356
22351
  }
22357
22352
 
22358
- function addCommand(command, callback, scope) {
22359
- command = command.toLowerCase();
22360
- commands.exec[command] = function(command, ui, value, args) {
22361
- return callback.call(scope || editor, ui, value, args);
22362
- };
22363
- }
22353
+ return container.childNodes[offset + relativeOffset];
22354
+ }
22364
22355
 
22365
- /**
22366
- * Returns true/false if the command is supported or not.
22367
- *
22368
- * @method queryCommandSupported
22369
- * @param {String} command Command that we check support for.
22370
- * @return {Boolean} true/false if the command is supported or not.
22371
- */
22372
- function queryCommandSupported(command) {
22373
- command = command.toLowerCase();
22356
+ function beforeAfter(before, node) {
22357
+ var range = node.ownerDocument.createRange();
22374
22358
 
22375
- if (commands.exec[command]) {
22376
- return true;
22377
- }
22359
+ if (before) {
22360
+ range.setStartBefore(node);
22361
+ range.setEndBefore(node);
22362
+ } else {
22363
+ range.setStartAfter(node);
22364
+ range.setEndAfter(node);
22365
+ }
22378
22366
 
22379
- // Browser commands
22380
- try {
22381
- return editor.getDoc().queryCommandSupported(command);
22382
- } catch (ex) {
22383
- // Fails sometimes see bug: 1896577
22384
- }
22367
+ return range;
22368
+ }
22385
22369
 
22386
- return false;
22387
- }
22370
+ function isNodesInSameBlock(rootNode, node1, node2) {
22371
+ return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode);
22372
+ }
22388
22373
 
22389
- function addQueryStateHandler(command, callback, scope) {
22390
- command = command.toLowerCase();
22391
- commands.state[command] = function() {
22392
- return callback.call(scope || editor);
22393
- };
22394
- }
22374
+ function lean(left, rootNode, node) {
22375
+ var sibling, siblingName;
22395
22376
 
22396
- function addQueryValueHandler(command, callback, scope) {
22397
- command = command.toLowerCase();
22398
- commands.value[command] = function() {
22399
- return callback.call(scope || editor);
22400
- };
22377
+ if (left) {
22378
+ siblingName = 'previousSibling';
22379
+ } else {
22380
+ siblingName = 'nextSibling';
22401
22381
  }
22402
22382
 
22403
- function hasCustomCommand(command) {
22404
- command = command.toLowerCase();
22405
- return !!commands.exec[command];
22406
- }
22383
+ while (node && node != rootNode) {
22384
+ sibling = node[siblingName];
22407
22385
 
22408
- // Expose public methods
22409
- extend(this, {
22410
- execCommand: execCommand,
22411
- queryCommandState: queryCommandState,
22412
- queryCommandValue: queryCommandValue,
22413
- queryCommandSupported: queryCommandSupported,
22414
- addCommands: addCommands,
22415
- addCommand: addCommand,
22416
- addQueryStateHandler: addQueryStateHandler,
22417
- addQueryValueHandler: addQueryValueHandler,
22418
- hasCustomCommand: hasCustomCommand
22419
- });
22386
+ if (isCaretContainer(sibling)) {
22387
+ sibling = sibling[siblingName];
22388
+ }
22420
22389
 
22421
- // Private methods
22390
+ if (isContentEditableFalse(sibling)) {
22391
+ if (isNodesInSameBlock(rootNode, sibling, node)) {
22392
+ return sibling;
22393
+ }
22422
22394
 
22423
- function execNativeCommand(command, ui, value) {
22424
- if (ui === undefined) {
22425
- ui = FALSE;
22395
+ break;
22426
22396
  }
22427
22397
 
22428
- if (value === undefined) {
22429
- value = null;
22398
+ if (isCaretCandidate(sibling)) {
22399
+ break;
22430
22400
  }
22431
22401
 
22432
- return editor.getDoc().execCommand(command, ui, value);
22402
+ node = node.parentNode;
22433
22403
  }
22434
22404
 
22435
- function isFormatMatch(name) {
22436
- return formatter.match(name);
22437
- }
22405
+ return null;
22406
+ }
22438
22407
 
22439
- function toggleFormat(name, value) {
22440
- formatter.toggle(name, value ? {value: value} : undefined);
22441
- editor.nodeChanged();
22442
- }
22408
+ var before = curry(beforeAfter, true);
22409
+ var after = curry(beforeAfter, false);
22443
22410
 
22444
- function storeSelection(type) {
22445
- bookmark = selection.getBookmark(type);
22446
- }
22411
+ function normalizeRange(direction, rootNode, range) {
22412
+ var node, container, offset, location;
22413
+ var leanLeft = curry(lean, true, rootNode);
22414
+ var leanRight = curry(lean, false, rootNode);
22447
22415
 
22448
- function restoreSelection() {
22449
- selection.moveToBookmark(bookmark);
22450
- }
22416
+ container = range.startContainer;
22417
+ offset = range.startOffset;
22451
22418
 
22452
- // Add execCommand overrides
22453
- addCommands({
22454
- // Ignore these, added for compatibility
22455
- 'mceResetDesignMode,mceBeginUndoLevel': function() {},
22419
+ if (CaretContainer.isCaretContainerBlock(container)) {
22420
+ if (!isElement(container)) {
22421
+ container = container.parentNode;
22422
+ }
22456
22423
 
22457
- // Add undo manager logic
22458
- 'mceEndUndoLevel,mceAddUndoLevel': function() {
22459
- editor.undoManager.add();
22460
- },
22424
+ location = container.getAttribute('data-mce-caret');
22461
22425
 
22462
- 'Cut,Copy,Paste': function(command) {
22463
- var doc = editor.getDoc(), failed;
22426
+ if (location == 'before') {
22427
+ node = container.nextSibling;
22428
+ if (isContentEditableFalse(node)) {
22429
+ return before(node);
22430
+ }
22431
+ }
22464
22432
 
22465
- // Try executing the native command
22466
- try {
22467
- execNativeCommand(command);
22468
- } catch (ex) {
22469
- // Command failed
22470
- failed = TRUE;
22433
+ if (location == 'after') {
22434
+ node = container.previousSibling;
22435
+ if (isContentEditableFalse(node)) {
22436
+ return after(node);
22471
22437
  }
22438
+ }
22439
+ }
22472
22440
 
22473
- // Present alert message about clipboard access not being available
22474
- if (failed || !doc.queryCommandSupported(command)) {
22475
- var msg = editor.translate(
22476
- "Your browser doesn't support direct access to the clipboard. " +
22477
- "Please use the Ctrl+X/C/V keyboard shortcuts instead."
22478
- );
22441
+ if (!range.collapsed) {
22442
+ return range;
22443
+ }
22479
22444
 
22480
- if (Env.mac) {
22481
- msg = msg.replace(/Ctrl\+/g, '\u2318+');
22445
+ if (NodeType.isText(container)) {
22446
+ if (isCaretContainer(container)) {
22447
+ if (direction === 1) {
22448
+ node = leanRight(container);
22449
+ if (node) {
22450
+ return before(node);
22482
22451
  }
22483
22452
 
22484
- editor.notificationManager.open({text: msg, type: 'error'});
22453
+ node = leanLeft(container);
22454
+ if (node) {
22455
+ return after(node);
22456
+ }
22485
22457
  }
22486
- },
22487
22458
 
22488
- // Override unlink command
22489
- unlink: function() {
22490
- if (selection.isCollapsed()) {
22491
- var elm = selection.getNode();
22492
- if (elm.tagName == 'A') {
22493
- editor.dom.remove(elm, true);
22459
+ if (direction === -1) {
22460
+ node = leanLeft(container);
22461
+ if (node) {
22462
+ return after(node);
22494
22463
  }
22495
22464
 
22496
- return;
22465
+ node = leanRight(container);
22466
+ if (node) {
22467
+ return before(node);
22468
+ }
22497
22469
  }
22498
22470
 
22499
- formatter.remove("link");
22500
- },
22501
-
22502
- // Override justify commands to use the text formatter engine
22503
- 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull,JustifyNone': function(command) {
22504
- var align = command.substring(7);
22471
+ return range;
22472
+ }
22505
22473
 
22506
- if (align == 'full') {
22507
- align = 'justify';
22474
+ if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) {
22475
+ if (direction === 1) {
22476
+ node = leanRight(container);
22477
+ if (node) {
22478
+ return before(node);
22479
+ }
22508
22480
  }
22509
22481
 
22510
- // Remove all other alignments first
22511
- each('left,center,right,justify'.split(','), function(name) {
22512
- if (align != name) {
22513
- formatter.remove('align' + name);
22514
- }
22515
- });
22482
+ return range;
22483
+ }
22516
22484
 
22517
- if (align != 'none') {
22518
- toggleFormat('align' + align);
22485
+ if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) {
22486
+ if (direction === -1) {
22487
+ node = leanLeft(container);
22488
+ if (node) {
22489
+ return after(node);
22490
+ }
22519
22491
  }
22520
- },
22521
22492
 
22522
- // Override list commands to fix WebKit bug
22523
- 'InsertUnorderedList,InsertOrderedList': function(command) {
22524
- var listElm, listParent;
22493
+ return range;
22494
+ }
22525
22495
 
22526
- execNativeCommand(command);
22496
+ if (offset === container.data.length) {
22497
+ node = leanRight(container);
22498
+ if (node) {
22499
+ return before(node);
22500
+ }
22527
22501
 
22528
- // WebKit produces lists within block elements so we need to split them
22529
- // we will replace the native list creation logic to custom logic later on
22530
- // TODO: Remove this when the list creation logic is removed
22531
- listElm = dom.getParent(selection.getNode(), 'ol,ul');
22532
- if (listElm) {
22533
- listParent = listElm.parentNode;
22502
+ return range;
22503
+ }
22534
22504
 
22535
- // If list is within a text block then split that block
22536
- if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
22537
- storeSelection();
22538
- dom.split(listParent, listElm);
22539
- restoreSelection();
22540
- }
22505
+ if (offset === 0) {
22506
+ node = leanLeft(container);
22507
+ if (node) {
22508
+ return after(node);
22541
22509
  }
22542
- },
22543
22510
 
22544
- // Override commands to use the text formatter engine
22545
- 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
22546
- toggleFormat(command);
22547
- },
22511
+ return range;
22512
+ }
22513
+ }
22548
22514
 
22549
- // Override commands to use the text formatter engine
22550
- 'ForeColor,HiliteColor,FontName': function(command, ui, value) {
22551
- toggleFormat(command, value);
22552
- },
22515
+ return range;
22516
+ }
22517
+
22518
+ function isNextToContentEditableFalse(relativeOffset, caretPosition) {
22519
+ return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition));
22520
+ }
22521
+
22522
+ return {
22523
+ isForwards: isForwards,
22524
+ isBackwards: isBackwards,
22525
+ findNode: findNode,
22526
+ getEditingHost: getEditingHost,
22527
+ getParentBlock: getParentBlock,
22528
+ isInSameBlock: isInSameBlock,
22529
+ isInSameEditingHost: isInSameEditingHost,
22530
+ isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0),
22531
+ isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1),
22532
+ normalizeRange: normalizeRange
22533
+ };
22534
+ });
22535
+
22536
+ // Included from: js/tinymce/classes/caret/CaretWalker.js
22537
+
22538
+ /**
22539
+ * CaretWalker.js
22540
+ *
22541
+ * Released under LGPL License.
22542
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
22543
+ *
22544
+ * License: http://www.tinymce.com/license
22545
+ * Contributing: http://www.tinymce.com/contributing
22546
+ */
22547
+
22548
+ /**
22549
+ * This module contains logic for moving around a virtual caret in logical order within a DOM element.
22550
+ *
22551
+ * It ignores the most obvious invalid caret locations such as within a script element or within a
22552
+ * contentEditable=false element but it will return locations that isn't possible to render visually.
22553
+ *
22554
+ * @private
22555
+ * @class tinymce.caret.CaretWalker
22556
+ * @example
22557
+ * var caretWalker = new CaretWalker(rootElm);
22558
+ *
22559
+ * var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
22560
+ * var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
22561
+ */
22562
+ define("tinymce/caret/CaretWalker", [
22563
+ "tinymce/dom/NodeType",
22564
+ "tinymce/caret/CaretCandidate",
22565
+ "tinymce/caret/CaretPosition",
22566
+ "tinymce/caret/CaretUtils",
22567
+ "tinymce/util/Arr",
22568
+ "tinymce/util/Fun"
22569
+ ], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) {
22570
+ var isContentEditableFalse = NodeType.isContentEditableFalse,
22571
+ isText = NodeType.isText,
22572
+ isElement = NodeType.isElement,
22573
+ isForwards = CaretUtils.isForwards,
22574
+ isBackwards = CaretUtils.isBackwards,
22575
+ isCaretCandidate = CaretCandidate.isCaretCandidate,
22576
+ isAtomic = CaretCandidate.isAtomic,
22577
+ isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate;
22578
+
22579
+ function getParents(node, rootNode) {
22580
+ var parents = [];
22581
+
22582
+ while (node && node != rootNode) {
22583
+ parents.push(node);
22584
+ node = node.parentNode;
22585
+ }
22586
+
22587
+ return parents;
22588
+ }
22589
+
22590
+ function nodeAtIndex(container, offset) {
22591
+ if (container.hasChildNodes() && offset < container.childNodes.length) {
22592
+ return container.childNodes[offset];
22593
+ }
22594
+
22595
+ return null;
22596
+ }
22597
+
22598
+ function getCaretCandidatePosition(direction, node) {
22599
+ if (isForwards(direction)) {
22600
+ if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) {
22601
+ return CaretPosition.before(node);
22602
+ }
22603
+
22604
+ if (isText(node)) {
22605
+ return CaretPosition(node, 0);
22606
+ }
22607
+ }
22608
+
22609
+ if (isBackwards(direction)) {
22610
+ if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) {
22611
+ return CaretPosition.after(node);
22612
+ }
22613
+
22614
+ if (isText(node)) {
22615
+ return CaretPosition(node, node.data.length);
22616
+ }
22617
+ }
22618
+
22619
+ if (isBackwards(direction)) {
22620
+ return CaretPosition.after(node);
22621
+ }
22622
+
22623
+ return CaretPosition.before(node);
22624
+ }
22625
+
22626
+ function findCaretPosition(direction, startCaretPosition, rootNode) {
22627
+ var container, offset, node, nextNode, innerNode,
22628
+ rootContentEditableFalseElm, caretPosition;
22629
+
22630
+ if (!isElement(rootNode) || !startCaretPosition) {
22631
+ return null;
22632
+ }
22633
+
22634
+ caretPosition = startCaretPosition;
22635
+ container = caretPosition.container();
22636
+ offset = caretPosition.offset();
22637
+
22638
+ if (isText(container)) {
22639
+ if (isBackwards(direction) && offset > 0) {
22640
+ return CaretPosition(container, --offset);
22641
+ }
22642
+
22643
+ if (isForwards(direction) && offset < container.length) {
22644
+ return CaretPosition(container, ++offset);
22645
+ }
22646
+
22647
+ node = container;
22648
+ } else {
22649
+ if (isBackwards(direction) && offset > 0) {
22650
+ nextNode = nodeAtIndex(container, offset - 1);
22651
+ if (isCaretCandidate(nextNode)) {
22652
+ if (!isAtomic(nextNode)) {
22653
+ innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
22654
+ if (innerNode) {
22655
+ if (isText(innerNode)) {
22656
+ return CaretPosition(innerNode, innerNode.data.length);
22657
+ }
22658
+
22659
+ return CaretPosition.after(innerNode);
22660
+ }
22661
+ }
22662
+
22663
+ if (isText(nextNode)) {
22664
+ return CaretPosition(nextNode, nextNode.data.length);
22665
+ }
22666
+
22667
+ return CaretPosition.before(nextNode);
22668
+ }
22669
+ }
22670
+
22671
+ if (isForwards(direction) && offset < container.childNodes.length) {
22672
+ nextNode = nodeAtIndex(container, offset);
22673
+ if (isCaretCandidate(nextNode)) {
22674
+ if (!isAtomic(nextNode)) {
22675
+ innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
22676
+ if (innerNode) {
22677
+ if (isText(innerNode)) {
22678
+ return CaretPosition(innerNode, 0);
22679
+ }
22680
+
22681
+ return CaretPosition.before(innerNode);
22682
+ }
22683
+ }
22684
+
22685
+ if (isText(nextNode)) {
22686
+ return CaretPosition(nextNode, 0);
22687
+ }
22688
+
22689
+ return CaretPosition.after(nextNode);
22690
+ }
22691
+ }
22692
+
22693
+ node = caretPosition.getNode();
22694
+ }
22695
+
22696
+ if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) {
22697
+ node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true);
22698
+ if (isEditableCaretCandidate(node)) {
22699
+ return getCaretCandidatePosition(direction, node);
22700
+ }
22701
+ }
22702
+
22703
+ nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode);
22704
+
22705
+ rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse));
22706
+ if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
22707
+ if (isForwards(direction)) {
22708
+ caretPosition = CaretPosition.after(rootContentEditableFalseElm);
22709
+ } else {
22710
+ caretPosition = CaretPosition.before(rootContentEditableFalseElm);
22711
+ }
22712
+
22713
+ return caretPosition;
22714
+ }
22715
+
22716
+ if (nextNode) {
22717
+ return getCaretCandidatePosition(direction, nextNode);
22718
+ }
22719
+
22720
+ return null;
22721
+ }
22722
+
22723
+ return function(rootNode) {
22724
+ return {
22725
+ /**
22726
+ * Returns the next logical caret position from the specificed input
22727
+ * caretPoisiton or null if there isn't any more positions left for example
22728
+ * at the end specified root element.
22729
+ *
22730
+ * @method next
22731
+ * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
22732
+ * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
22733
+ */
22734
+ next: function(caretPosition) {
22735
+ return findCaretPosition(1, caretPosition, rootNode);
22736
+ },
22737
+
22738
+ /**
22739
+ * Returns the previous logical caret position from the specificed input
22740
+ * caretPoisiton or null if there isn't any more positions left for example
22741
+ * at the end specified root element.
22742
+ *
22743
+ * @method prev
22744
+ * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
22745
+ * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
22746
+ */
22747
+ prev: function(caretPosition) {
22748
+ return findCaretPosition(-1, caretPosition, rootNode);
22749
+ }
22750
+ };
22751
+ };
22752
+ });
22753
+
22754
+ // Included from: js/tinymce/classes/EditorCommands.js
22755
+
22756
+ /**
22757
+ * EditorCommands.js
22758
+ *
22759
+ * Released under LGPL License.
22760
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
22761
+ *
22762
+ * License: http://www.tinymce.com/license
22763
+ * Contributing: http://www.tinymce.com/contributing
22764
+ */
22765
+
22766
+ /**
22767
+ * This class enables you to add custom editor commands and it contains
22768
+ * overrides for native browser commands to address various bugs and issues.
22769
+ *
22770
+ * @class tinymce.EditorCommands
22771
+ */
22772
+ define("tinymce/EditorCommands", [
22773
+ "tinymce/html/Serializer",
22774
+ "tinymce/Env",
22775
+ "tinymce/util/Tools",
22776
+ "tinymce/dom/ElementUtils",
22777
+ "tinymce/dom/RangeUtils",
22778
+ "tinymce/dom/TreeWalker",
22779
+ "tinymce/caret/CaretWalker",
22780
+ "tinymce/caret/CaretPosition",
22781
+ "tinymce/dom/NodeType"
22782
+ ], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker, CaretWalker, CaretPosition, NodeType) {
22783
+ // Added for compression purposes
22784
+ var each = Tools.each, extend = Tools.extend;
22785
+ var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
22786
+ var isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
22787
+ var TRUE = true, FALSE = false, isTableCell = NodeType.matchNodeNames('td th');
22788
+
22789
+ return function(editor) {
22790
+ var dom, selection, formatter,
22791
+ commands = {state: {}, exec: {}, value: {}},
22792
+ settings = editor.settings,
22793
+ bookmark;
22794
+
22795
+ editor.on('PreInit', function() {
22796
+ dom = editor.dom;
22797
+ selection = editor.selection;
22798
+ settings = editor.settings;
22799
+ formatter = editor.formatter;
22800
+ });
22801
+
22802
+ /**
22803
+ * Executes the specified command.
22804
+ *
22805
+ * @method execCommand
22806
+ * @param {String} command Command to execute.
22807
+ * @param {Boolean} ui Optional user interface state.
22808
+ * @param {Object} value Optional value for command.
22809
+ * @param {Object} args Optional extra arguments to the execCommand.
22810
+ * @return {Boolean} true/false if the command was found or not.
22811
+ */
22812
+ function execCommand(command, ui, value, args) {
22813
+ var func, customCommand, state = 0;
22814
+
22815
+ if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
22816
+ editor.focus();
22817
+ }
22818
+
22819
+ args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
22820
+ if (args.isDefaultPrevented()) {
22821
+ return false;
22822
+ }
22823
+
22824
+ customCommand = command.toLowerCase();
22825
+ if ((func = commands.exec[customCommand])) {
22826
+ func(customCommand, ui, value);
22827
+ editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22828
+ return true;
22829
+ }
22830
+
22831
+ // Plugin commands
22832
+ each(editor.plugins, function(p) {
22833
+ if (p.execCommand && p.execCommand(command, ui, value)) {
22834
+ editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22835
+ state = true;
22836
+ return false;
22837
+ }
22838
+ });
22839
+
22840
+ if (state) {
22841
+ return state;
22842
+ }
22843
+
22844
+ // Theme commands
22845
+ if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
22846
+ editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22847
+ return true;
22848
+ }
22849
+
22850
+ // Browser commands
22851
+ try {
22852
+ state = editor.getDoc().execCommand(command, ui, value);
22853
+ } catch (ex) {
22854
+ // Ignore old IE errors
22855
+ }
22856
+
22857
+ if (state) {
22858
+ editor.fire('ExecCommand', {command: command, ui: ui, value: value});
22859
+ return true;
22860
+ }
22861
+
22862
+ return false;
22863
+ }
22864
+
22865
+ /**
22866
+ * Queries the current state for a command for example if the current selection is "bold".
22867
+ *
22868
+ * @method queryCommandState
22869
+ * @param {String} command Command to check the state of.
22870
+ * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
22871
+ */
22872
+ function queryCommandState(command) {
22873
+ var func;
22874
+
22875
+ // Is hidden then return undefined
22876
+ if (editor._isHidden()) {
22877
+ return;
22878
+ }
22879
+
22880
+ command = command.toLowerCase();
22881
+ if ((func = commands.state[command])) {
22882
+ return func(command);
22883
+ }
22884
+
22885
+ // Browser commands
22886
+ try {
22887
+ return editor.getDoc().queryCommandState(command);
22888
+ } catch (ex) {
22889
+ // Fails sometimes see bug: 1896577
22890
+ }
22891
+
22892
+ return false;
22893
+ }
22894
+
22895
+ /**
22896
+ * Queries the command value for example the current fontsize.
22897
+ *
22898
+ * @method queryCommandValue
22899
+ * @param {String} command Command to check the value of.
22900
+ * @return {Object} Command value of false if it's not found.
22901
+ */
22902
+ function queryCommandValue(command) {
22903
+ var func;
22904
+
22905
+ // Is hidden then return undefined
22906
+ if (editor._isHidden()) {
22907
+ return;
22908
+ }
22909
+
22910
+ command = command.toLowerCase();
22911
+ if ((func = commands.value[command])) {
22912
+ return func(command);
22913
+ }
22914
+
22915
+ // Browser commands
22916
+ try {
22917
+ return editor.getDoc().queryCommandValue(command);
22918
+ } catch (ex) {
22919
+ // Fails sometimes see bug: 1896577
22920
+ }
22921
+ }
22922
+
22923
+ /**
22924
+ * Adds commands to the command collection.
22925
+ *
22926
+ * @method addCommands
22927
+ * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
22928
+ * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
22929
+ */
22930
+ function addCommands(command_list, type) {
22931
+ type = type || 'exec';
22932
+
22933
+ each(command_list, function(callback, command) {
22934
+ each(command.toLowerCase().split(','), function(command) {
22935
+ commands[type][command] = callback;
22936
+ });
22937
+ });
22938
+ }
22939
+
22940
+ function addCommand(command, callback, scope) {
22941
+ command = command.toLowerCase();
22942
+ commands.exec[command] = function(command, ui, value, args) {
22943
+ return callback.call(scope || editor, ui, value, args);
22944
+ };
22945
+ }
22946
+
22947
+ /**
22948
+ * Returns true/false if the command is supported or not.
22949
+ *
22950
+ * @method queryCommandSupported
22951
+ * @param {String} command Command that we check support for.
22952
+ * @return {Boolean} true/false if the command is supported or not.
22953
+ */
22954
+ function queryCommandSupported(command) {
22955
+ command = command.toLowerCase();
22956
+
22957
+ if (commands.exec[command]) {
22958
+ return true;
22959
+ }
22960
+
22961
+ // Browser commands
22962
+ try {
22963
+ return editor.getDoc().queryCommandSupported(command);
22964
+ } catch (ex) {
22965
+ // Fails sometimes see bug: 1896577
22966
+ }
22967
+
22968
+ return false;
22969
+ }
22970
+
22971
+ function addQueryStateHandler(command, callback, scope) {
22972
+ command = command.toLowerCase();
22973
+ commands.state[command] = function() {
22974
+ return callback.call(scope || editor);
22975
+ };
22976
+ }
22977
+
22978
+ function addQueryValueHandler(command, callback, scope) {
22979
+ command = command.toLowerCase();
22980
+ commands.value[command] = function() {
22981
+ return callback.call(scope || editor);
22982
+ };
22983
+ }
22984
+
22985
+ function hasCustomCommand(command) {
22986
+ command = command.toLowerCase();
22987
+ return !!commands.exec[command];
22988
+ }
22989
+
22990
+ // Expose public methods
22991
+ extend(this, {
22992
+ execCommand: execCommand,
22993
+ queryCommandState: queryCommandState,
22994
+ queryCommandValue: queryCommandValue,
22995
+ queryCommandSupported: queryCommandSupported,
22996
+ addCommands: addCommands,
22997
+ addCommand: addCommand,
22998
+ addQueryStateHandler: addQueryStateHandler,
22999
+ addQueryValueHandler: addQueryValueHandler,
23000
+ hasCustomCommand: hasCustomCommand
23001
+ });
23002
+
23003
+ // Private methods
23004
+
23005
+ function execNativeCommand(command, ui, value) {
23006
+ if (ui === undefined) {
23007
+ ui = FALSE;
23008
+ }
23009
+
23010
+ if (value === undefined) {
23011
+ value = null;
23012
+ }
23013
+
23014
+ return editor.getDoc().execCommand(command, ui, value);
23015
+ }
23016
+
23017
+ function isFormatMatch(name) {
23018
+ return formatter.match(name);
23019
+ }
23020
+
23021
+ function toggleFormat(name, value) {
23022
+ formatter.toggle(name, value ? {value: value} : undefined);
23023
+ editor.nodeChanged();
23024
+ }
23025
+
23026
+ function storeSelection(type) {
23027
+ bookmark = selection.getBookmark(type);
23028
+ }
23029
+
23030
+ function restoreSelection() {
23031
+ selection.moveToBookmark(bookmark);
23032
+ }
23033
+
23034
+ // Add execCommand overrides
23035
+ addCommands({
23036
+ // Ignore these, added for compatibility
23037
+ 'mceResetDesignMode,mceBeginUndoLevel': function() {},
23038
+
23039
+ // Add undo manager logic
23040
+ 'mceEndUndoLevel,mceAddUndoLevel': function() {
23041
+ editor.undoManager.add();
23042
+ },
23043
+
23044
+ 'Cut,Copy,Paste': function(command) {
23045
+ var doc = editor.getDoc(), failed;
23046
+
23047
+ // Try executing the native command
23048
+ try {
23049
+ execNativeCommand(command);
23050
+ } catch (ex) {
23051
+ // Command failed
23052
+ failed = TRUE;
23053
+ }
23054
+
23055
+ // Present alert message about clipboard access not being available
23056
+ if (failed || !doc.queryCommandSupported(command)) {
23057
+ var msg = editor.translate(
23058
+ "Your browser doesn't support direct access to the clipboard. " +
23059
+ "Please use the Ctrl+X/C/V keyboard shortcuts instead."
23060
+ );
23061
+
23062
+ if (Env.mac) {
23063
+ msg = msg.replace(/Ctrl\+/g, '\u2318+');
23064
+ }
23065
+
23066
+ editor.notificationManager.open({text: msg, type: 'error'});
23067
+ }
23068
+ },
23069
+
23070
+ // Override unlink command
23071
+ unlink: function() {
23072
+ if (selection.isCollapsed()) {
23073
+ var elm = selection.getNode();
23074
+ if (elm.tagName == 'A') {
23075
+ editor.dom.remove(elm, true);
23076
+ }
23077
+
23078
+ return;
23079
+ }
23080
+
23081
+ formatter.remove("link");
23082
+ },
23083
+
23084
+ // Override justify commands to use the text formatter engine
23085
+ 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull,JustifyNone': function(command) {
23086
+ var align = command.substring(7);
23087
+
23088
+ if (align == 'full') {
23089
+ align = 'justify';
23090
+ }
23091
+
23092
+ // Remove all other alignments first
23093
+ each('left,center,right,justify'.split(','), function(name) {
23094
+ if (align != name) {
23095
+ formatter.remove('align' + name);
23096
+ }
23097
+ });
23098
+
23099
+ if (align != 'none') {
23100
+ toggleFormat('align' + align);
23101
+ }
23102
+ },
23103
+
23104
+ // Override list commands to fix WebKit bug
23105
+ 'InsertUnorderedList,InsertOrderedList': function(command) {
23106
+ var listElm, listParent;
23107
+
23108
+ execNativeCommand(command);
23109
+
23110
+ // WebKit produces lists within block elements so we need to split them
23111
+ // we will replace the native list creation logic to custom logic later on
23112
+ // TODO: Remove this when the list creation logic is removed
23113
+ listElm = dom.getParent(selection.getNode(), 'ol,ul');
23114
+ if (listElm) {
23115
+ listParent = listElm.parentNode;
23116
+
23117
+ // If list is within a text block then split that block
23118
+ if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
23119
+ storeSelection();
23120
+ dom.split(listParent, listElm);
23121
+ restoreSelection();
23122
+ }
23123
+ }
23124
+ },
23125
+
23126
+ // Override commands to use the text formatter engine
23127
+ 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
23128
+ toggleFormat(command);
23129
+ },
23130
+
23131
+ // Override commands to use the text formatter engine
23132
+ 'ForeColor,HiliteColor,FontName': function(command, ui, value) {
23133
+ toggleFormat(command, value);
23134
+ },
22553
23135
 
22554
23136
  FontSize: function(command, ui, value) {
22555
23137
  var fontClasses, fontSizes;
@@ -22704,7 +23286,7 @@ define("tinymce/EditorCommands", [
22704
23286
  }
22705
23287
 
22706
23288
  function moveSelectionToMarker(marker) {
22707
- var parentEditableFalseElm;
23289
+ var parentEditableFalseElm, parentNode, nextRng;
22708
23290
 
22709
23291
  function getContentEditableFalseParent(node) {
22710
23292
  var root = editor.getBody();
@@ -22754,8 +23336,34 @@ define("tinymce/EditorCommands", [
22754
23336
  rng.setEndBefore(marker);
22755
23337
  }
22756
23338
 
23339
+ function findNextCaretRng(rng) {
23340
+ var caretPos = CaretPosition.fromRangeStart(rng);
23341
+ var caretWalker = new CaretWalker(editor.getBody());
23342
+
23343
+ caretPos = caretWalker.next(caretPos);
23344
+ if (caretPos) {
23345
+ return caretPos.toRange();
23346
+ }
23347
+ }
23348
+
22757
23349
  // Remove the marker node and set the new range
23350
+ parentNode = marker.parentNode;
22758
23351
  dom.remove(marker);
23352
+
23353
+ if (dom.isEmpty(parentNode) && dom.isBlock(parentNode)) {
23354
+ editor.$(parentNode).empty();
23355
+
23356
+ rng.setStart(parentNode, 0);
23357
+ rng.setEnd(parentNode, 0);
23358
+
23359
+ if (!isTableCell(parentNode) && (nextRng = findNextCaretRng(rng))) {
23360
+ rng = nextRng;
23361
+ dom.remove(parentNode);
23362
+ } else {
23363
+ dom.add(parentNode, dom.create('br', {'data-mce-bogus': '1'}));
23364
+ }
23365
+ }
23366
+
22759
23367
  selection.setRng(rng);
22760
23368
  }
22761
23369
 
@@ -29518,6 +30126,7 @@ define("tinymce/ui/Window", [
29518
30126
 
29519
30127
  setTimeout(function() {
29520
30128
  self.classes.add('in');
30129
+ self.fire('open');
29521
30130
  }, 0);
29522
30131
 
29523
30132
  self._super();
@@ -32626,499 +33235,71 @@ define("tinymce/EditorObservable", [
32626
33235
  },
32627
33236
 
32628
33237
  /**
32629
- * Toggles a native event on/off this is called by the EventDispatcher when
32630
- * the first native event handler is added and when the last native event handler is removed.
32631
- *
32632
- * @private
32633
- */
32634
- toggleNativeEvent: function(name, state) {
32635
- var self = this;
32636
-
32637
- // Never bind focus/blur since the FocusManager fakes those
32638
- if (name == "focus" || name == "blur") {
32639
- return;
32640
- }
32641
-
32642
- if (state) {
32643
- if (self.initialized) {
32644
- bindEventDelegate(self, name);
32645
- } else {
32646
- if (!self._pendingNativeEvents) {
32647
- self._pendingNativeEvents = [name];
32648
- } else {
32649
- self._pendingNativeEvents.push(name);
32650
- }
32651
- }
32652
- } else if (self.initialized) {
32653
- self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
32654
- delete self.delegates[name];
32655
- }
32656
- },
32657
-
32658
- /**
32659
- * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
32660
- *
32661
- * @private
32662
- */
32663
- unbindAllNativeEvents: function() {
32664
- var self = this, name;
32665
-
32666
- if (self.delegates) {
32667
- for (name in self.delegates) {
32668
- self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
32669
- }
32670
-
32671
- delete self.delegates;
32672
- }
32673
-
32674
- if (!self.inline) {
32675
- self.getBody().onload = null;
32676
- self.dom.unbind(self.getWin());
32677
- self.dom.unbind(self.getDoc());
32678
- }
32679
-
32680
- self.dom.unbind(self.getBody());
32681
- self.dom.unbind(self.getContainer());
32682
- }
32683
- };
32684
-
32685
- EditorObservable = Tools.extend({}, Observable, EditorObservable);
32686
-
32687
- return EditorObservable;
32688
- });
32689
-
32690
- // Included from: js/tinymce/classes/Mode.js
32691
-
32692
- /**
32693
- * Mode.js
32694
- *
32695
- * Released under LGPL License.
32696
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
32697
- *
32698
- * License: http://www.tinymce.com/license
32699
- * Contributing: http://www.tinymce.com/contributing
32700
- */
32701
-
32702
- /**
32703
- * Mode switcher logic.
32704
- *
32705
- * @private
32706
- * @class tinymce.Mode
32707
- */
32708
- define("tinymce/Mode", [], function() {
32709
- function setEditorCommandState(editor, cmd, state) {
32710
- try {
32711
- editor.getDoc().execCommand(cmd, false, state);
32712
- } catch (ex) {
32713
- // Ignore
32714
- }
32715
- }
32716
-
32717
- function setMode(editor, mode) {
32718
- var currentMode = editor.readonly ? 'readonly' : 'design';
32719
-
32720
- if (mode == currentMode) {
32721
- return;
32722
- }
32723
-
32724
- if (mode == 'readonly') {
32725
- editor.selection.controlSelection.hideResizeRect();
32726
- editor.readonly = true;
32727
- editor.getBody().contentEditable = false;
32728
- } else {
32729
- editor.readonly = false;
32730
- editor.getBody().contentEditable = true;
32731
- setEditorCommandState(editor, "StyleWithCSS", false);
32732
- setEditorCommandState(editor, "enableInlineTableEditing", false);
32733
- setEditorCommandState(editor, "enableObjectResizing", false);
32734
- editor.focus();
32735
- editor.nodeChanged();
32736
- }
32737
-
32738
- // Event is NOT preventable
32739
- editor.fire('SwitchMode', {mode: mode});
32740
- }
32741
-
32742
- return {
32743
- setMode: setMode
32744
- };
32745
- });
32746
-
32747
- // Included from: js/tinymce/classes/Shortcuts.js
32748
-
32749
- /**
32750
- * Shortcuts.js
32751
- *
32752
- * Released under LGPL License.
32753
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
32754
- *
32755
- * License: http://www.tinymce.com/license
32756
- * Contributing: http://www.tinymce.com/contributing
32757
- */
32758
-
32759
- /**
32760
- * Contains all logic for handling of keyboard shortcuts.
32761
- *
32762
- * @class tinymce.Shortcuts
32763
- * @example
32764
- * editor.shortcuts.add('ctrl+a', function() {});
32765
- * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
32766
- * editor.shortcuts.add('ctrl+alt+a', function() {});
32767
- * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
32768
- */
32769
- define("tinymce/Shortcuts", [
32770
- "tinymce/util/Tools",
32771
- "tinymce/Env"
32772
- ], function(Tools, Env) {
32773
- var each = Tools.each, explode = Tools.explode;
32774
-
32775
- var keyCodeLookup = {
32776
- "f9": 120,
32777
- "f10": 121,
32778
- "f11": 122
32779
- };
32780
-
32781
- var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
32782
-
32783
- return function(editor) {
32784
- var self = this, shortcuts = {};
32785
-
32786
- function createShortcut(pattern, desc, cmdFunc, scope) {
32787
- var id, key, shortcut;
32788
-
32789
- shortcut = {
32790
- func: cmdFunc,
32791
- scope: scope || editor,
32792
- desc: editor.translate(desc)
32793
- };
32794
-
32795
- // Parse modifiers and keys ctrl+alt+b for example
32796
- each(explode(pattern, '+'), function(value) {
32797
- if (value in modifierNames) {
32798
- shortcut[value] = true;
32799
- } else {
32800
- // Allow numeric keycodes like ctrl+219 for ctrl+[
32801
- if (/^[0-9]{2,}$/.test(value)) {
32802
- shortcut.keyCode = parseInt(value, 10);
32803
- } else {
32804
- shortcut.charCode = value.charCodeAt(0);
32805
- shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
32806
- }
32807
- }
32808
- });
32809
-
32810
- // Generate unique id for modifier combination and set default state for unused modifiers
32811
- id = [shortcut.keyCode];
32812
- for (key in modifierNames) {
32813
- if (shortcut[key]) {
32814
- id.push(key);
32815
- } else {
32816
- shortcut[key] = false;
32817
- }
32818
- }
32819
- shortcut.id = id.join(',');
32820
-
32821
- // Handle special access modifier differently depending on Mac/Win
32822
- if (shortcut.access) {
32823
- shortcut.alt = true;
32824
-
32825
- if (Env.mac) {
32826
- shortcut.ctrl = true;
32827
- } else {
32828
- shortcut.shift = true;
32829
- }
32830
- }
32831
-
32832
- // Handle special meta modifier differently depending on Mac/Win
32833
- if (shortcut.meta) {
32834
- if (Env.mac) {
32835
- shortcut.meta = true;
32836
- } else {
32837
- shortcut.ctrl = true;
32838
- shortcut.meta = false;
32839
- }
32840
- }
32841
-
32842
- return shortcut;
32843
- }
32844
-
32845
- editor.on('keyup keypress keydown', function(e) {
32846
- if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) {
32847
- each(shortcuts, function(shortcut) {
32848
- if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
32849
- return;
32850
- }
32851
-
32852
- if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
32853
- return;
32854
- }
32855
-
32856
- if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
32857
- e.preventDefault();
32858
-
32859
- if (e.type == "keydown") {
32860
- shortcut.func.call(shortcut.scope);
32861
- }
32862
-
32863
- return true;
32864
- }
32865
- });
32866
- }
32867
- });
32868
-
32869
- /**
32870
- * Adds a keyboard shortcut for some command or function.
32871
- *
32872
- * @method addShortcut
32873
- * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
32874
- * @param {String} desc Text description for the command.
32875
- * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
32876
- * @param {Object} scope Optional scope to execute the function in.
32877
- * @return {Boolean} true/false state if the shortcut was added or not.
32878
- */
32879
- self.add = function(pattern, desc, cmdFunc, scope) {
32880
- var cmd;
32881
-
32882
- cmd = cmdFunc;
32883
-
32884
- if (typeof cmdFunc === 'string') {
32885
- cmdFunc = function() {
32886
- editor.execCommand(cmd, false, null);
32887
- };
32888
- } else if (Tools.isArray(cmd)) {
32889
- cmdFunc = function() {
32890
- editor.execCommand(cmd[0], cmd[1], cmd[2]);
32891
- };
32892
- }
32893
-
32894
- each(explode(pattern.toLowerCase()), function(pattern) {
32895
- var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
32896
- shortcuts[shortcut.id] = shortcut;
32897
- });
32898
-
32899
- return true;
32900
- };
32901
-
32902
- /**
32903
- * Remove a keyboard shortcut by pattern.
32904
- *
32905
- * @method remove
32906
- * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
32907
- * @return {Boolean} true/false state if the shortcut was removed or not.
32908
- */
32909
- self.remove = function(pattern) {
32910
- var shortcut = createShortcut(pattern);
32911
-
32912
- if (shortcuts[shortcut.id]) {
32913
- delete shortcuts[shortcut.id];
32914
- return true;
32915
- }
32916
-
32917
- return false;
32918
- };
32919
- };
32920
- });
32921
-
32922
- // Included from: js/tinymce/classes/file/Uploader.js
32923
-
32924
- /**
32925
- * Uploader.js
32926
- *
32927
- * Released under LGPL License.
32928
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
32929
- *
32930
- * License: http://www.tinymce.com/license
32931
- * Contributing: http://www.tinymce.com/contributing
32932
- */
32933
-
32934
- /**
32935
- * Upload blobs or blob infos to the specified URL or handler.
32936
- *
32937
- * @private
32938
- * @class tinymce.file.Uploader
32939
- * @example
32940
- * var uploader = new Uploader({
32941
- * url: '/upload.php',
32942
- * basePath: '/base/path',
32943
- * credentials: true,
32944
- * handler: function(data, success, failure) {
32945
- * ...
32946
- * }
32947
- * });
32948
- *
32949
- * uploader.upload(blobInfos).then(function(result) {
32950
- * ...
32951
- * });
32952
- */
32953
- define("tinymce/file/Uploader", [
32954
- "tinymce/util/Promise",
32955
- "tinymce/util/Tools",
32956
- "tinymce/util/Fun"
32957
- ], function(Promise, Tools, Fun) {
32958
- return function(settings) {
32959
- var cachedPromises = {};
32960
-
32961
- function fileName(blobInfo) {
32962
- var ext, extensions;
32963
-
32964
- extensions = {
32965
- 'image/jpeg': 'jpg',
32966
- 'image/jpg': 'jpg',
32967
- 'image/gif': 'gif',
32968
- 'image/png': 'png'
32969
- };
32970
-
32971
- ext = extensions[blobInfo.blob().type.toLowerCase()] || 'dat';
32972
-
32973
- return blobInfo.id() + '.' + ext;
32974
- }
32975
-
32976
- function pathJoin(path1, path2) {
32977
- if (path1) {
32978
- return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '');
32979
- }
32980
-
32981
- return path2;
32982
- }
32983
-
32984
- function blobInfoToData(blobInfo) {
32985
- return {
32986
- id: blobInfo.id,
32987
- blob: blobInfo.blob,
32988
- base64: blobInfo.base64,
32989
- filename: Fun.constant(fileName(blobInfo))
32990
- };
32991
- }
32992
-
32993
- function defaultHandler(blobInfo, success, failure, openNotification) {
32994
- var xhr, formData, notification;
32995
-
32996
- xhr = new XMLHttpRequest();
32997
- xhr.open('POST', settings.url);
32998
- xhr.withCredentials = settings.credentials;
32999
-
33000
- notification = openNotification();
33001
-
33002
- xhr.upload.onprogress = function(e) {
33003
- var percentLoaded = Math.round(e.loaded / e.total * 100);
33004
- notification.progressBar.value(percentLoaded);
33005
- };
33006
-
33007
- xhr.onerror = function() {
33008
- notification.close();
33009
- failure("Image upload failed due to a XHR Transport error. Code: " + xhr.status);
33010
- };
33011
-
33012
- xhr.onload = function() {
33013
- var json;
33014
-
33015
- notification.close();
33016
-
33017
- if (xhr.status != 200) {
33018
- failure("HTTP Error: " + xhr.status);
33019
- return;
33020
- }
33021
-
33022
- json = JSON.parse(xhr.responseText);
33023
-
33024
- if (!json || typeof json.location != "string") {
33025
- failure("Invalid JSON: " + xhr.responseText);
33026
- return;
33027
- }
33028
-
33029
- success(pathJoin(settings.basePath, json.location));
33030
- };
33031
-
33032
- formData = new FormData();
33033
- formData.append('file', blobInfo.blob(), fileName(blobInfo));
33034
-
33035
- xhr.send(formData);
33036
- }
33037
-
33038
- function noUpload() {
33039
- return new Promise(function(resolve) {
33040
- resolve([]);
33041
- });
33042
- }
33043
-
33044
- function interpretResult(promise) {
33045
- return promise.then(function(result) {
33046
- return result;
33047
- })['catch'](function(error) {
33048
- return error;
33049
- });
33050
- }
33051
-
33052
- function registerPromise(handler, id, blobInfo) {
33053
- var response = handler(blobInfo);
33054
- var promise = interpretResult(response);
33055
- delete cachedPromises[id];
33056
- cachedPromises[id] = promise;
33057
- return promise;
33058
- }
33059
-
33060
- function collectUploads(blobInfos, uploadBlobInfo) {
33061
- return Tools.map(blobInfos, function(blobInfo) {
33062
- var id = blobInfo.id();
33063
- return cachedPromises[id] ? cachedPromises[id] : registerPromise(uploadBlobInfo, id, blobInfo);
33064
- });
33065
- }
33238
+ * Toggles a native event on/off this is called by the EventDispatcher when
33239
+ * the first native event handler is added and when the last native event handler is removed.
33240
+ *
33241
+ * @private
33242
+ */
33243
+ toggleNativeEvent: function(name, state) {
33244
+ var self = this;
33066
33245
 
33067
- function uploadBlobs(blobInfos, openNotification) {
33068
- function uploadBlobInfo(blobInfo) {
33069
- return new Promise(function(resolve) {
33070
- var handler = settings.handler;
33246
+ // Never bind focus/blur since the FocusManager fakes those
33247
+ if (name == "focus" || name == "blur") {
33248
+ return;
33249
+ }
33071
33250
 
33072
- try {
33073
- handler(blobInfoToData(blobInfo), function(url) {
33074
- resolve({
33075
- url: url,
33076
- blobInfo: blobInfo,
33077
- status: true
33078
- });
33079
- }, function(failure) {
33080
- resolve({
33081
- url: '',
33082
- blobInfo: blobInfo,
33083
- status: false,
33084
- error: failure
33085
- });
33086
- }, openNotification);
33087
- } catch (ex) {
33088
- resolve({
33089
- url: '',
33090
- blobInfo: blobInfo,
33091
- status: false,
33092
- error: ex.message
33093
- });
33251
+ if (state) {
33252
+ if (self.initialized) {
33253
+ bindEventDelegate(self, name);
33254
+ } else {
33255
+ if (!self._pendingNativeEvents) {
33256
+ self._pendingNativeEvents = [name];
33257
+ } else {
33258
+ self._pendingNativeEvents.push(name);
33094
33259
  }
33095
- });
33260
+ }
33261
+ } else if (self.initialized) {
33262
+ self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
33263
+ delete self.delegates[name];
33096
33264
  }
33265
+ },
33097
33266
 
33098
- var promises = collectUploads(blobInfos, uploadBlobInfo);
33099
- return Promise.all(promises);
33100
- }
33267
+ /**
33268
+ * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
33269
+ *
33270
+ * @private
33271
+ */
33272
+ unbindAllNativeEvents: function() {
33273
+ var self = this, name;
33101
33274
 
33102
- function upload(blobInfos, openNotification) {
33103
- return (!settings.url && settings.handler === defaultHandler) ? noUpload() : uploadBlobs(blobInfos, openNotification);
33104
- }
33275
+ if (self.delegates) {
33276
+ for (name in self.delegates) {
33277
+ self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
33278
+ }
33105
33279
 
33106
- settings = Tools.extend({
33107
- credentials: false,
33108
- // We are adding a notify argument to this (at the moment, until it doesn't work)
33109
- handler: defaultHandler
33110
- }, settings);
33280
+ delete self.delegates;
33281
+ }
33111
33282
 
33112
- return {
33113
- upload: upload
33114
- };
33283
+ if (!self.inline) {
33284
+ self.getBody().onload = null;
33285
+ self.dom.unbind(self.getWin());
33286
+ self.dom.unbind(self.getDoc());
33287
+ }
33288
+
33289
+ self.dom.unbind(self.getBody());
33290
+ self.dom.unbind(self.getContainer());
33291
+ }
33115
33292
  };
33293
+
33294
+ EditorObservable = Tools.extend({}, Observable, EditorObservable);
33295
+
33296
+ return EditorObservable;
33116
33297
  });
33117
33298
 
33118
- // Included from: js/tinymce/classes/file/Conversions.js
33299
+ // Included from: js/tinymce/classes/Mode.js
33119
33300
 
33120
33301
  /**
33121
- * Conversions.js
33302
+ * Mode.js
33122
33303
  *
33123
33304
  * Released under LGPL License.
33124
33305
  * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -33128,106 +33309,90 @@ define("tinymce/file/Uploader", [
33128
33309
  */
33129
33310
 
33130
33311
  /**
33131
- * Converts blob/uris back and forth.
33312
+ * Mode switcher logic.
33132
33313
  *
33133
33314
  * @private
33134
- * @class tinymce.file.Conversions
33315
+ * @class tinymce.Mode
33135
33316
  */
33136
- define("tinymce/file/Conversions", [
33137
- "tinymce/util/Promise"
33138
- ], function(Promise) {
33139
- function blobUriToBlob(url) {
33140
- return new Promise(function(resolve) {
33141
- var xhr = new XMLHttpRequest();
33142
-
33143
- xhr.open('GET', url, true);
33144
- xhr.responseType = 'blob';
33145
-
33146
- xhr.onload = function() {
33147
- if (this.status == 200) {
33148
- resolve(this.response);
33149
- }
33150
- };
33151
-
33152
- xhr.send();
33153
- });
33154
- }
33155
-
33156
- function parseDataUri(uri) {
33157
- var type, matches;
33158
-
33159
- uri = decodeURIComponent(uri).split(',');
33160
-
33161
- matches = /data:([^;]+)/.exec(uri[0]);
33162
- if (matches) {
33163
- type = matches[1];
33317
+ define("tinymce/Mode", [], function() {
33318
+ function setEditorCommandState(editor, cmd, state) {
33319
+ try {
33320
+ editor.getDoc().execCommand(cmd, false, state);
33321
+ } catch (ex) {
33322
+ // Ignore
33164
33323
  }
33165
-
33166
- return {
33167
- type: type,
33168
- data: uri[1]
33169
- };
33170
33324
  }
33171
33325
 
33172
- function dataUriToBlob(uri) {
33173
- return new Promise(function(resolve) {
33174
- var str, arr, i;
33326
+ function clickBlocker(editor) {
33327
+ var target, handler;
33175
33328
 
33176
- uri = parseDataUri(uri);
33329
+ target = editor.getBody();
33177
33330
 
33178
- // Might throw error if data isn't proper base64
33179
- try {
33180
- str = atob(uri.data);
33181
- } catch (e) {
33182
- resolve(new Blob([]));
33183
- return;
33331
+ handler = function(e) {
33332
+ if (editor.dom.getParents(e.target, 'a').length > 0) {
33333
+ e.preventDefault();
33184
33334
  }
33335
+ };
33185
33336
 
33186
- arr = new Uint8Array(str.length);
33337
+ editor.dom.bind(target, 'click', handler);
33187
33338
 
33188
- for (i = 0; i < arr.length; i++) {
33189
- arr[i] = str.charCodeAt(i);
33339
+ return {
33340
+ unbind: function() {
33341
+ editor.dom.unbind(target, 'click', handler);
33190
33342
  }
33191
-
33192
- resolve(new Blob([arr], {type: uri.type}));
33193
- });
33343
+ };
33194
33344
  }
33195
33345
 
33196
- function uriToBlob(url) {
33197
- if (url.indexOf('blob:') === 0) {
33198
- return blobUriToBlob(url);
33346
+ function toggleReadOnly(editor, state) {
33347
+ if (editor._clickBlocker) {
33348
+ editor._clickBlocker.unbind();
33349
+ editor._clickBlocker = null;
33199
33350
  }
33200
33351
 
33201
- if (url.indexOf('data:') === 0) {
33202
- return dataUriToBlob(url);
33352
+ if (state) {
33353
+ editor._clickBlocker = clickBlocker(editor);
33354
+ editor.selection.controlSelection.hideResizeRect();
33355
+ editor.readonly = true;
33356
+ editor.getBody().contentEditable = false;
33357
+ } else {
33358
+ editor.readonly = false;
33359
+ editor.getBody().contentEditable = true;
33360
+ setEditorCommandState(editor, "StyleWithCSS", false);
33361
+ setEditorCommandState(editor, "enableInlineTableEditing", false);
33362
+ setEditorCommandState(editor, "enableObjectResizing", false);
33363
+ editor.focus();
33364
+ editor.nodeChanged();
33203
33365
  }
33204
-
33205
- return null;
33206
33366
  }
33207
33367
 
33208
- function blobToDataUri(blob) {
33209
- return new Promise(function(resolve) {
33210
- var reader = new FileReader();
33368
+ function setMode(editor, mode) {
33369
+ var currentMode = editor.readonly ? 'readonly' : 'design';
33211
33370
 
33212
- reader.onloadend = function() {
33213
- resolve(reader.result);
33214
- };
33371
+ if (mode == currentMode) {
33372
+ return;
33373
+ }
33215
33374
 
33216
- reader.readAsDataURL(blob);
33217
- });
33375
+ if (editor.initialized) {
33376
+ toggleReadOnly(editor, mode == 'readonly');
33377
+ } else {
33378
+ editor.on('init', function() {
33379
+ toggleReadOnly(editor, mode == 'readonly');
33380
+ });
33381
+ }
33382
+
33383
+ // Event is NOT preventable
33384
+ editor.fire('SwitchMode', {mode: mode});
33218
33385
  }
33219
33386
 
33220
33387
  return {
33221
- uriToBlob: uriToBlob,
33222
- blobToDataUri: blobToDataUri,
33223
- parseDataUri: parseDataUri
33388
+ setMode: setMode
33224
33389
  };
33225
33390
  });
33226
33391
 
33227
- // Included from: js/tinymce/classes/file/ImageScanner.js
33392
+ // Included from: js/tinymce/classes/Shortcuts.js
33228
33393
 
33229
33394
  /**
33230
- * ImageScanner.js
33395
+ * Shortcuts.js
33231
33396
  *
33232
33397
  * Released under LGPL License.
33233
33398
  * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -33237,220 +33402,172 @@ define("tinymce/file/Conversions", [
33237
33402
  */
33238
33403
 
33239
33404
  /**
33240
- * Finds images with data uris or blob uris. If data uris are found it will convert them into blob uris.
33405
+ * Contains all logic for handling of keyboard shortcuts.
33241
33406
  *
33242
- * @private
33243
- * @class tinymce.file.ImageScanner
33407
+ * @class tinymce.Shortcuts
33408
+ * @example
33409
+ * editor.shortcuts.add('ctrl+a', function() {});
33410
+ * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
33411
+ * editor.shortcuts.add('ctrl+alt+a', function() {});
33412
+ * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
33244
33413
  */
33245
- define("tinymce/file/ImageScanner", [
33246
- "tinymce/util/Promise",
33247
- "tinymce/util/Arr",
33248
- "tinymce/util/Fun",
33249
- "tinymce/file/Conversions",
33414
+ define("tinymce/Shortcuts", [
33415
+ "tinymce/util/Tools",
33250
33416
  "tinymce/Env"
33251
- ], function(Promise, Arr, Fun, Conversions, Env) {
33252
- var count = 0;
33417
+ ], function(Tools, Env) {
33418
+ var each = Tools.each, explode = Tools.explode;
33253
33419
 
33254
- return function(blobCache) {
33255
- var cachedPromises = {};
33420
+ var keyCodeLookup = {
33421
+ "f9": 120,
33422
+ "f10": 121,
33423
+ "f11": 122
33424
+ };
33256
33425
 
33257
- function findAll(elm, predicate) {
33258
- var images, promises;
33426
+ var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
33259
33427
 
33260
- function imageToBlobInfo(img, resolve) {
33261
- var base64, blobInfo;
33428
+ return function(editor) {
33429
+ var self = this, shortcuts = {};
33262
33430
 
33263
- if (img.src.indexOf('blob:') === 0) {
33264
- blobInfo = blobCache.getByUri(img.src);
33431
+ function createShortcut(pattern, desc, cmdFunc, scope) {
33432
+ var id, key, shortcut;
33265
33433
 
33266
- if (blobInfo) {
33267
- resolve({
33268
- image: img,
33269
- blobInfo: blobInfo
33270
- });
33271
- }
33434
+ shortcut = {
33435
+ func: cmdFunc,
33436
+ scope: scope || editor,
33437
+ desc: editor.translate(desc)
33438
+ };
33272
33439
 
33273
- return;
33440
+ // Parse modifiers and keys ctrl+alt+b for example
33441
+ each(explode(pattern, '+'), function(value) {
33442
+ if (value in modifierNames) {
33443
+ shortcut[value] = true;
33444
+ } else {
33445
+ // Allow numeric keycodes like ctrl+219 for ctrl+[
33446
+ if (/^[0-9]{2,}$/.test(value)) {
33447
+ shortcut.keyCode = parseInt(value, 10);
33448
+ } else {
33449
+ shortcut.charCode = value.charCodeAt(0);
33450
+ shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
33451
+ }
33274
33452
  }
33453
+ });
33275
33454
 
33276
- base64 = Conversions.parseDataUri(img.src).data;
33277
- blobInfo = blobCache.findFirst(function(cachedBlobInfo) {
33278
- return cachedBlobInfo.base64() === base64;
33279
- });
33280
-
33281
- if (blobInfo) {
33282
- resolve({
33283
- image: img,
33284
- blobInfo: blobInfo
33285
- });
33455
+ // Generate unique id for modifier combination and set default state for unused modifiers
33456
+ id = [shortcut.keyCode];
33457
+ for (key in modifierNames) {
33458
+ if (shortcut[key]) {
33459
+ id.push(key);
33286
33460
  } else {
33287
- Conversions.uriToBlob(img.src).then(function(blob) {
33288
- var blobInfoId = 'blobid' + (count++),
33289
- blobInfo = blobCache.create(blobInfoId, blob, base64);
33290
-
33291
- blobCache.add(blobInfo);
33292
-
33293
- resolve({
33294
- image: img,
33295
- blobInfo: blobInfo
33296
- });
33297
- });
33461
+ shortcut[key] = false;
33298
33462
  }
33299
33463
  }
33464
+ shortcut.id = id.join(',');
33300
33465
 
33301
- if (!predicate) {
33302
- predicate = Fun.constant(true);
33303
- }
33304
-
33305
- images = Arr.filter(elm.getElementsByTagName('img'), function(img) {
33306
- var src = img.src;
33307
-
33308
- if (!Env.fileApi) {
33309
- return false;
33310
- }
33311
-
33312
- if (img.hasAttribute('data-mce-bogus')) {
33313
- return false;
33314
- }
33466
+ // Handle special access modifier differently depending on Mac/Win
33467
+ if (shortcut.access) {
33468
+ shortcut.alt = true;
33315
33469
 
33316
- if (img.hasAttribute('data-mce-placeholder')) {
33317
- return false;
33470
+ if (Env.mac) {
33471
+ shortcut.ctrl = true;
33472
+ } else {
33473
+ shortcut.shift = true;
33318
33474
  }
33475
+ }
33319
33476
 
33320
- if (!src || src == Env.transparentSrc) {
33321
- return false;
33477
+ // Handle special meta modifier differently depending on Mac/Win
33478
+ if (shortcut.meta) {
33479
+ if (Env.mac) {
33480
+ shortcut.meta = true;
33481
+ } else {
33482
+ shortcut.ctrl = true;
33483
+ shortcut.meta = false;
33322
33484
  }
33485
+ }
33323
33486
 
33324
- if (src.indexOf('blob:') === 0) {
33325
- return true;
33326
- }
33487
+ return shortcut;
33488
+ }
33327
33489
 
33328
- if (src.indexOf('data:') === 0) {
33329
- return predicate(img);
33330
- }
33490
+ editor.on('keyup keypress keydown', function(e) {
33491
+ if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) {
33492
+ each(shortcuts, function(shortcut) {
33493
+ if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
33494
+ return;
33495
+ }
33331
33496
 
33332
- return false;
33333
- });
33497
+ if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
33498
+ return;
33499
+ }
33334
33500
 
33335
- promises = Arr.map(images, function(img) {
33336
- var newPromise;
33501
+ if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
33502
+ e.preventDefault();
33337
33503
 
33338
- if (cachedPromises[img.src]) {
33339
- // Since the cached promise will return the cached image
33340
- // We need to wrap it and resolve with the actual image
33341
- return new Promise(function(resolve) {
33342
- cachedPromises[img.src].then(function(imageInfo) {
33343
- resolve({
33344
- image: img,
33345
- blobInfo: imageInfo.blobInfo
33346
- });
33347
- });
33348
- });
33349
- }
33504
+ if (e.type == "keydown") {
33505
+ shortcut.func.call(shortcut.scope);
33506
+ }
33350
33507
 
33351
- newPromise = new Promise(function(resolve) {
33352
- imageToBlobInfo(img, resolve);
33353
- }).then(function(result) {
33354
- delete cachedPromises[result.image.src];
33355
- return result;
33356
- })['catch'](function(error) {
33357
- delete cachedPromises[img.src];
33358
- return error;
33508
+ return true;
33509
+ }
33359
33510
  });
33511
+ }
33512
+ });
33360
33513
 
33361
- cachedPromises[img.src] = newPromise;
33362
-
33363
- return newPromise;
33364
- });
33365
-
33366
- return Promise.all(promises);
33367
- }
33368
-
33369
- return {
33370
- findAll: findAll
33371
- };
33372
- };
33373
- });
33374
-
33375
- // Included from: js/tinymce/classes/file/BlobCache.js
33376
-
33377
- /**
33378
- * BlobCache.js
33379
- *
33380
- * Released under LGPL License.
33381
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
33382
- *
33383
- * License: http://www.tinymce.com/license
33384
- * Contributing: http://www.tinymce.com/contributing
33385
- */
33386
-
33387
- /**
33388
- * Hold blob info objects where a blob has extra internal information.
33389
- *
33390
- * @private
33391
- * @class tinymce.file.BlobCache
33392
- */
33393
- define("tinymce/file/BlobCache", [
33394
- "tinymce/util/Arr",
33395
- "tinymce/util/Fun"
33396
- ], function(Arr, Fun) {
33397
- return function() {
33398
- var cache = [], constant = Fun.constant;
33514
+ /**
33515
+ * Adds a keyboard shortcut for some command or function.
33516
+ *
33517
+ * @method addShortcut
33518
+ * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
33519
+ * @param {String} desc Text description for the command.
33520
+ * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
33521
+ * @param {Object} scope Optional scope to execute the function in.
33522
+ * @return {Boolean} true/false state if the shortcut was added or not.
33523
+ */
33524
+ self.add = function(pattern, desc, cmdFunc, scope) {
33525
+ var cmd;
33399
33526
 
33400
- function create(id, blob, base64) {
33401
- return {
33402
- id: constant(id),
33403
- blob: constant(blob),
33404
- base64: constant(base64),
33405
- blobUri: constant(URL.createObjectURL(blob))
33406
- };
33407
- }
33527
+ cmd = cmdFunc;
33408
33528
 
33409
- function add(blobInfo) {
33410
- if (!get(blobInfo.id())) {
33411
- cache.push(blobInfo);
33529
+ if (typeof cmdFunc === 'string') {
33530
+ cmdFunc = function() {
33531
+ editor.execCommand(cmd, false, null);
33532
+ };
33533
+ } else if (Tools.isArray(cmd)) {
33534
+ cmdFunc = function() {
33535
+ editor.execCommand(cmd[0], cmd[1], cmd[2]);
33536
+ };
33412
33537
  }
33413
- }
33414
33538
 
33415
- function get(id) {
33416
- return findFirst(function(cachedBlobInfo) {
33417
- return cachedBlobInfo.id() === id;
33539
+ each(explode(pattern.toLowerCase()), function(pattern) {
33540
+ var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
33541
+ shortcuts[shortcut.id] = shortcut;
33418
33542
  });
33419
- }
33420
-
33421
- function findFirst(predicate) {
33422
- return Arr.filter(cache, predicate)[0];
33423
- }
33424
33543
 
33425
- function getByUri(blobUri) {
33426
- return findFirst(function(blobInfo) {
33427
- return blobInfo.blobUri() == blobUri;
33428
- });
33429
- }
33544
+ return true;
33545
+ };
33430
33546
 
33431
- function destroy() {
33432
- Arr.each(cache, function(cachedBlobInfo) {
33433
- URL.revokeObjectURL(cachedBlobInfo.blobUri());
33434
- });
33547
+ /**
33548
+ * Remove a keyboard shortcut by pattern.
33549
+ *
33550
+ * @method remove
33551
+ * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
33552
+ * @return {Boolean} true/false state if the shortcut was removed or not.
33553
+ */
33554
+ self.remove = function(pattern) {
33555
+ var shortcut = createShortcut(pattern);
33435
33556
 
33436
- cache = [];
33437
- }
33557
+ if (shortcuts[shortcut.id]) {
33558
+ delete shortcuts[shortcut.id];
33559
+ return true;
33560
+ }
33438
33561
 
33439
- return {
33440
- create: create,
33441
- add: add,
33442
- get: get,
33443
- getByUri: getByUri,
33444
- findFirst: findFirst,
33445
- destroy: destroy
33562
+ return false;
33446
33563
  };
33447
33564
  };
33448
33565
  });
33449
33566
 
33450
- // Included from: js/tinymce/classes/EditorUpload.js
33567
+ // Included from: js/tinymce/classes/file/Uploader.js
33451
33568
 
33452
33569
  /**
33453
- * EditorUpload.js
33570
+ * Uploader.js
33454
33571
  *
33455
33572
  * Released under LGPL License.
33456
33573
  * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -33460,191 +33577,193 @@ define("tinymce/file/BlobCache", [
33460
33577
  */
33461
33578
 
33462
33579
  /**
33463
- * Handles image uploads, updates undo stack and patches over various internal functions.
33580
+ * Upload blobs or blob infos to the specified URL or handler.
33581
+ *
33582
+ * @private
33583
+ * @class tinymce.file.Uploader
33584
+ * @example
33585
+ * var uploader = new Uploader({
33586
+ * url: '/upload.php',
33587
+ * basePath: '/base/path',
33588
+ * credentials: true,
33589
+ * handler: function(data, success, failure) {
33590
+ * ...
33591
+ * }
33592
+ * });
33464
33593
  *
33465
- * @private
33466
- * @class tinymce.EditorUpload
33594
+ * uploader.upload(blobInfos).then(function(result) {
33595
+ * ...
33596
+ * });
33467
33597
  */
33468
- define("tinymce/EditorUpload", [
33469
- "tinymce/util/Arr",
33470
- "tinymce/file/Uploader",
33471
- "tinymce/file/ImageScanner",
33472
- "tinymce/file/BlobCache"
33473
- ], function(Arr, Uploader, ImageScanner, BlobCache) {
33474
- return function(editor) {
33475
- var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings;
33598
+ define("tinymce/file/Uploader", [
33599
+ "tinymce/util/Promise",
33600
+ "tinymce/util/Tools",
33601
+ "tinymce/util/Fun"
33602
+ ], function(Promise, Tools, Fun) {
33603
+ return function(settings) {
33604
+ var cachedPromises = {};
33476
33605
 
33477
- function aliveGuard(callback) {
33478
- return function(result) {
33479
- if (editor.selection) {
33480
- return callback(result);
33481
- }
33606
+ function fileName(blobInfo) {
33607
+ var ext, extensions;
33482
33608
 
33483
- return [];
33609
+ extensions = {
33610
+ 'image/jpeg': 'jpg',
33611
+ 'image/jpg': 'jpg',
33612
+ 'image/gif': 'gif',
33613
+ 'image/png': 'png'
33484
33614
  };
33485
- }
33486
-
33487
- // Replaces strings without regexps to avoid FF regexp to big issue
33488
- function replaceString(content, search, replace) {
33489
- var index = 0;
33490
33615
 
33491
- do {
33492
- index = content.indexOf(search, index);
33493
-
33494
- if (index !== -1) {
33495
- content = content.substring(0, index) + replace + content.substr(index + search.length);
33496
- index += replace.length - search.length + 1;
33497
- }
33498
- } while (index !== -1);
33616
+ ext = extensions[blobInfo.blob().type.toLowerCase()] || 'dat';
33499
33617
 
33500
- return content;
33618
+ return blobInfo.id() + '.' + ext;
33501
33619
  }
33502
33620
 
33503
- function replaceImageUrl(content, targetUrl, replacementUrl) {
33504
- content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"');
33505
- content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
33506
-
33507
- return content;
33508
- }
33621
+ function pathJoin(path1, path2) {
33622
+ if (path1) {
33623
+ return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '');
33624
+ }
33509
33625
 
33510
- function replaceUrlInUndoStack(targetUrl, replacementUrl) {
33511
- Arr.each(editor.undoManager.data, function(level) {
33512
- level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
33513
- });
33626
+ return path2;
33514
33627
  }
33515
33628
 
33516
- function openNotification() {
33517
- return editor.notificationManager.open({
33518
- text: editor.translate('Image uploading...'),
33519
- type: 'info',
33520
- timeout: -1,
33521
- progressBar: true
33522
- });
33629
+ function blobInfoToData(blobInfo) {
33630
+ return {
33631
+ id: blobInfo.id,
33632
+ blob: blobInfo.blob,
33633
+ base64: blobInfo.base64,
33634
+ filename: Fun.constant(fileName(blobInfo))
33635
+ };
33523
33636
  }
33524
33637
 
33525
- function uploadImages(callback) {
33526
- if (!uploader) {
33527
- uploader = new Uploader({
33528
- url: settings.images_upload_url,
33529
- basePath: settings.images_upload_base_path,
33530
- credentials: settings.images_upload_credentials,
33531
- handler: settings.images_upload_handler
33532
- });
33533
- }
33638
+ function defaultHandler(blobInfo, success, failure, openNotification) {
33639
+ var xhr, formData, notification;
33534
33640
 
33535
- return scanForImages().then(aliveGuard(function(imageInfos) {
33536
- var blobInfos;
33641
+ xhr = new XMLHttpRequest();
33642
+ xhr.open('POST', settings.url);
33643
+ xhr.withCredentials = settings.credentials;
33537
33644
 
33538
- blobInfos = Arr.map(imageInfos, function(imageInfo) {
33539
- return imageInfo.blobInfo;
33540
- });
33645
+ notification = openNotification();
33541
33646
 
33542
- return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) {
33543
- result = Arr.map(result, function(uploadInfo, index) {
33544
- var image = imageInfos[index].image;
33647
+ xhr.upload.onprogress = function(e) {
33648
+ var percentLoaded = Math.round(e.loaded / e.total * 100);
33649
+ notification.progressBar.value(percentLoaded);
33650
+ };
33545
33651
 
33546
- if (uploadInfo.status) {
33547
- replaceUrlInUndoStack(image.src, uploadInfo.url);
33652
+ xhr.onerror = function() {
33653
+ notification.close();
33654
+ failure("Image upload failed due to a XHR Transport error. Code: " + xhr.status);
33655
+ };
33548
33656
 
33549
- editor.$(image).attr({
33550
- src: uploadInfo.url,
33551
- 'data-mce-src': editor.convertURL(uploadInfo.url, 'src')
33552
- });
33553
- }
33657
+ xhr.onload = function() {
33658
+ var json;
33554
33659
 
33555
- return {
33556
- element: image,
33557
- status: uploadInfo.status
33558
- };
33559
- });
33660
+ notification.close();
33560
33661
 
33561
- if (callback) {
33562
- callback(result);
33563
- }
33662
+ if (xhr.status != 200) {
33663
+ failure("HTTP Error: " + xhr.status);
33664
+ return;
33665
+ }
33564
33666
 
33565
- return result;
33566
- }));
33567
- }));
33568
- }
33667
+ json = JSON.parse(xhr.responseText);
33569
33668
 
33570
- function uploadImagesAuto(callback) {
33571
- if (settings.automatic_uploads !== false) {
33572
- return uploadImages(callback);
33573
- }
33574
- }
33669
+ if (!json || typeof json.location != "string") {
33670
+ failure("Invalid JSON: " + xhr.responseText);
33671
+ return;
33672
+ }
33575
33673
 
33576
- function scanForImages() {
33577
- if (!imageScanner) {
33578
- imageScanner = new ImageScanner(blobCache);
33579
- }
33674
+ success(pathJoin(settings.basePath, json.location));
33675
+ };
33580
33676
 
33581
- return imageScanner.findAll(editor.getBody(), settings.images_dataimg_filter).then(aliveGuard(function(result) {
33582
- Arr.each(result, function(resultItem) {
33583
- replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
33584
- resultItem.image.src = resultItem.blobInfo.blobUri();
33585
- });
33677
+ formData = new FormData();
33678
+ formData.append('file', blobInfo.blob(), fileName(blobInfo));
33586
33679
 
33587
- return result;
33588
- }));
33680
+ xhr.send(formData);
33589
33681
  }
33590
33682
 
33591
- function destroy() {
33592
- blobCache.destroy();
33593
- imageScanner = uploader = null;
33683
+ function noUpload() {
33684
+ return new Promise(function(resolve) {
33685
+ resolve([]);
33686
+ });
33594
33687
  }
33595
33688
 
33596
- function replaceBlobWithBase64(content) {
33597
- return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) {
33598
- var blobInfo = blobCache.getByUri(blobUri);
33599
-
33600
- if (!blobInfo) {
33601
- blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) {
33602
- return result || editor.editorUpload.blobCache.getByUri(blobUri);
33603
- }, null);
33604
- }
33689
+ function interpretResult(promise) {
33690
+ return promise.then(function(result) {
33691
+ return result;
33692
+ })['catch'](function(error) {
33693
+ return error;
33694
+ });
33695
+ }
33605
33696
 
33606
- if (blobInfo) {
33607
- return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"';
33608
- }
33697
+ function registerPromise(handler, id, blobInfo) {
33698
+ var response = handler(blobInfo);
33699
+ var promise = interpretResult(response);
33700
+ delete cachedPromises[id];
33701
+ cachedPromises[id] = promise;
33702
+ return promise;
33703
+ }
33609
33704
 
33610
- return match;
33705
+ function collectUploads(blobInfos, uploadBlobInfo) {
33706
+ return Tools.map(blobInfos, function(blobInfo) {
33707
+ var id = blobInfo.id();
33708
+ return cachedPromises[id] ? cachedPromises[id] : registerPromise(uploadBlobInfo, id, blobInfo);
33611
33709
  });
33612
33710
  }
33613
33711
 
33614
- editor.on('setContent', function() {
33615
- if (editor.settings.automatic_uploads !== false) {
33616
- uploadImagesAuto();
33617
- } else {
33618
- scanForImages();
33712
+ function uploadBlobs(blobInfos, openNotification) {
33713
+ function uploadBlobInfo(blobInfo) {
33714
+ return new Promise(function(resolve) {
33715
+ var handler = settings.handler;
33716
+
33717
+ try {
33718
+ handler(blobInfoToData(blobInfo), function(url) {
33719
+ resolve({
33720
+ url: url,
33721
+ blobInfo: blobInfo,
33722
+ status: true
33723
+ });
33724
+ }, function(failure) {
33725
+ resolve({
33726
+ url: '',
33727
+ blobInfo: blobInfo,
33728
+ status: false,
33729
+ error: failure
33730
+ });
33731
+ }, openNotification);
33732
+ } catch (ex) {
33733
+ resolve({
33734
+ url: '',
33735
+ blobInfo: blobInfo,
33736
+ status: false,
33737
+ error: ex.message
33738
+ });
33739
+ }
33740
+ });
33619
33741
  }
33620
- });
33621
33742
 
33622
- editor.on('RawSaveContent', function(e) {
33623
- e.content = replaceBlobWithBase64(e.content);
33624
- });
33743
+ var promises = collectUploads(blobInfos, uploadBlobInfo);
33744
+ return Promise.all(promises);
33745
+ }
33625
33746
 
33626
- editor.on('getContent', function(e) {
33627
- if (e.source_view || e.format == 'raw') {
33628
- return;
33629
- }
33747
+ function upload(blobInfos, openNotification) {
33748
+ return (!settings.url && settings.handler === defaultHandler) ? noUpload() : uploadBlobs(blobInfos, openNotification);
33749
+ }
33630
33750
 
33631
- e.content = replaceBlobWithBase64(e.content);
33632
- });
33751
+ settings = Tools.extend({
33752
+ credentials: false,
33753
+ // We are adding a notify argument to this (at the moment, until it doesn't work)
33754
+ handler: defaultHandler
33755
+ }, settings);
33633
33756
 
33634
33757
  return {
33635
- blobCache: blobCache,
33636
- uploadImages: uploadImages,
33637
- uploadImagesAuto: uploadImagesAuto,
33638
- scanForImages: scanForImages,
33639
- destroy: destroy
33758
+ upload: upload
33640
33759
  };
33641
33760
  };
33642
33761
  });
33643
33762
 
33644
- // Included from: js/tinymce/classes/caret/CaretUtils.js
33763
+ // Included from: js/tinymce/classes/file/Conversions.js
33645
33764
 
33646
33765
  /**
33647
- * CaretUtils.js
33766
+ * Conversions.js
33648
33767
  *
33649
33768
  * Released under LGPL License.
33650
33769
  * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -33654,302 +33773,254 @@ define("tinymce/EditorUpload", [
33654
33773
  */
33655
33774
 
33656
33775
  /**
33657
- * Utility functions shared by the caret logic.
33776
+ * Converts blob/uris back and forth.
33658
33777
  *
33659
33778
  * @private
33660
- * @class tinymce.caret.CaretUtils
33779
+ * @class tinymce.file.Conversions
33661
33780
  */
33662
- define("tinymce/caret/CaretUtils", [
33663
- "tinymce/util/Fun",
33664
- "tinymce/dom/TreeWalker",
33665
- "tinymce/dom/NodeType",
33666
- "tinymce/caret/CaretPosition",
33667
- "tinymce/caret/CaretContainer",
33668
- "tinymce/caret/CaretCandidate"
33669
- ], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) {
33670
- var isContentEditableTrue = NodeType.isContentEditableTrue,
33671
- isContentEditableFalse = NodeType.isContentEditableFalse,
33672
- isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'),
33673
- isCaretContainer = CaretContainer.isCaretContainer,
33674
- curry = Fun.curry,
33675
- isElement = NodeType.isElement,
33676
- isCaretCandidate = CaretCandidate.isCaretCandidate;
33677
-
33678
- function isForwards(direction) {
33679
- return direction > 0;
33680
- }
33681
-
33682
- function isBackwards(direction) {
33683
- return direction < 0;
33684
- }
33781
+ define("tinymce/file/Conversions", [
33782
+ "tinymce/util/Promise"
33783
+ ], function(Promise) {
33784
+ function blobUriToBlob(url) {
33785
+ return new Promise(function(resolve) {
33786
+ var xhr = new XMLHttpRequest();
33685
33787
 
33686
- function findNode(node, direction, predicateFn, rootNode, shallow) {
33687
- var walker = new TreeWalker(node, rootNode);
33788
+ xhr.open('GET', url, true);
33789
+ xhr.responseType = 'blob';
33688
33790
 
33689
- if (isBackwards(direction)) {
33690
- if (isContentEditableFalse(node)) {
33691
- node = walker.prev(true);
33692
- if (predicateFn(node)) {
33693
- return node;
33791
+ xhr.onload = function() {
33792
+ if (this.status == 200) {
33793
+ resolve(this.response);
33694
33794
  }
33695
- }
33795
+ };
33696
33796
 
33697
- while ((node = walker.prev(shallow))) {
33698
- if (predicateFn(node)) {
33699
- return node;
33700
- }
33701
- }
33702
- }
33797
+ xhr.send();
33798
+ });
33799
+ }
33703
33800
 
33704
- if (isForwards(direction)) {
33705
- if (isContentEditableFalse(node)) {
33706
- node = walker.next(true);
33707
- if (predicateFn(node)) {
33708
- return node;
33709
- }
33710
- }
33801
+ function parseDataUri(uri) {
33802
+ var type, matches;
33711
33803
 
33712
- while ((node = walker.next(shallow))) {
33713
- if (predicateFn(node)) {
33714
- return node;
33715
- }
33716
- }
33804
+ uri = decodeURIComponent(uri).split(',');
33805
+
33806
+ matches = /data:([^;]+)/.exec(uri[0]);
33807
+ if (matches) {
33808
+ type = matches[1];
33717
33809
  }
33718
33810
 
33719
- return null;
33811
+ return {
33812
+ type: type,
33813
+ data: uri[1]
33814
+ };
33720
33815
  }
33721
33816
 
33722
- function getEditingHost(node, rootNode) {
33723
- for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
33724
- if (isContentEditableTrue(node)) {
33725
- return node;
33726
- }
33727
- }
33817
+ function dataUriToBlob(uri) {
33818
+ return new Promise(function(resolve) {
33819
+ var str, arr, i;
33728
33820
 
33729
- return rootNode;
33730
- }
33821
+ uri = parseDataUri(uri);
33731
33822
 
33732
- function getParentBlock(node, rootNode) {
33733
- while (node && node != rootNode) {
33734
- if (isBlockLike(node)) {
33735
- return node;
33823
+ // Might throw error if data isn't proper base64
33824
+ try {
33825
+ str = atob(uri.data);
33826
+ } catch (e) {
33827
+ resolve(new Blob([]));
33828
+ return;
33736
33829
  }
33737
33830
 
33738
- node = node.parentNode;
33739
- }
33740
-
33741
- return null;
33742
- }
33831
+ arr = new Uint8Array(str.length);
33743
33832
 
33744
- function isInSameBlock(caretPosition1, caretPosition2, rootNode) {
33745
- return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode);
33746
- }
33833
+ for (i = 0; i < arr.length; i++) {
33834
+ arr[i] = str.charCodeAt(i);
33835
+ }
33747
33836
 
33748
- function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) {
33749
- return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode);
33837
+ resolve(new Blob([arr], {type: uri.type}));
33838
+ });
33750
33839
  }
33751
33840
 
33752
- function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) {
33753
- var container, offset;
33754
-
33755
- if (!caretPosition) {
33756
- return null;
33841
+ function uriToBlob(url) {
33842
+ if (url.indexOf('blob:') === 0) {
33843
+ return blobUriToBlob(url);
33757
33844
  }
33758
33845
 
33759
- container = caretPosition.container();
33760
- offset = caretPosition.offset();
33761
-
33762
- if (!isElement(container)) {
33763
- return null;
33846
+ if (url.indexOf('data:') === 0) {
33847
+ return dataUriToBlob(url);
33764
33848
  }
33765
33849
 
33766
- return container.childNodes[offset + relativeOffset];
33850
+ return null;
33767
33851
  }
33768
33852
 
33769
- function beforeAfter(before, node) {
33770
- var range = node.ownerDocument.createRange();
33771
-
33772
- if (before) {
33773
- range.setStartBefore(node);
33774
- range.setEndBefore(node);
33775
- } else {
33776
- range.setStartAfter(node);
33777
- range.setEndAfter(node);
33778
- }
33853
+ function blobToDataUri(blob) {
33854
+ return new Promise(function(resolve) {
33855
+ var reader = new FileReader();
33779
33856
 
33780
- return range;
33781
- }
33857
+ reader.onloadend = function() {
33858
+ resolve(reader.result);
33859
+ };
33782
33860
 
33783
- function isNodesInSameBlock(rootNode, node1, node2) {
33784
- return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode);
33861
+ reader.readAsDataURL(blob);
33862
+ });
33785
33863
  }
33786
33864
 
33787
- function lean(left, rootNode, node) {
33788
- var sibling, siblingName;
33789
-
33790
- if (left) {
33791
- siblingName = 'previousSibling';
33792
- } else {
33793
- siblingName = 'nextSibling';
33794
- }
33795
-
33796
- while (node && node != rootNode) {
33797
- sibling = node[siblingName];
33865
+ return {
33866
+ uriToBlob: uriToBlob,
33867
+ blobToDataUri: blobToDataUri,
33868
+ parseDataUri: parseDataUri
33869
+ };
33870
+ });
33798
33871
 
33799
- if (isCaretContainer(sibling)) {
33800
- sibling = sibling[siblingName];
33801
- }
33872
+ // Included from: js/tinymce/classes/file/ImageScanner.js
33802
33873
 
33803
- if (isContentEditableFalse(sibling)) {
33804
- if (isNodesInSameBlock(rootNode, sibling, node)) {
33805
- return sibling;
33806
- }
33874
+ /**
33875
+ * ImageScanner.js
33876
+ *
33877
+ * Released under LGPL License.
33878
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
33879
+ *
33880
+ * License: http://www.tinymce.com/license
33881
+ * Contributing: http://www.tinymce.com/contributing
33882
+ */
33807
33883
 
33808
- break;
33809
- }
33884
+ /**
33885
+ * Finds images with data uris or blob uris. If data uris are found it will convert them into blob uris.
33886
+ *
33887
+ * @private
33888
+ * @class tinymce.file.ImageScanner
33889
+ */
33890
+ define("tinymce/file/ImageScanner", [
33891
+ "tinymce/util/Promise",
33892
+ "tinymce/util/Arr",
33893
+ "tinymce/util/Fun",
33894
+ "tinymce/file/Conversions",
33895
+ "tinymce/Env"
33896
+ ], function(Promise, Arr, Fun, Conversions, Env) {
33897
+ var count = 0;
33810
33898
 
33811
- if (isCaretCandidate(sibling)) {
33812
- break;
33813
- }
33899
+ return function(blobCache) {
33900
+ var cachedPromises = {};
33814
33901
 
33815
- node = node.parentNode;
33816
- }
33902
+ function findAll(elm, predicate) {
33903
+ var images, promises;
33817
33904
 
33818
- return null;
33819
- }
33905
+ function imageToBlobInfo(img, resolve) {
33906
+ var base64, blobInfo;
33820
33907
 
33821
- var before = curry(beforeAfter, true);
33822
- var after = curry(beforeAfter, false);
33908
+ if (img.src.indexOf('blob:') === 0) {
33909
+ blobInfo = blobCache.getByUri(img.src);
33823
33910
 
33824
- function normalizeRange(direction, rootNode, range) {
33825
- var node, container, offset, location;
33826
- var leanLeft = curry(lean, true, rootNode);
33827
- var leanRight = curry(lean, false, rootNode);
33911
+ if (blobInfo) {
33912
+ resolve({
33913
+ image: img,
33914
+ blobInfo: blobInfo
33915
+ });
33916
+ }
33828
33917
 
33829
- container = range.startContainer;
33830
- offset = range.startOffset;
33918
+ return;
33919
+ }
33831
33920
 
33832
- if (CaretContainer.isCaretContainerBlock(container)) {
33833
- if (!isElement(container)) {
33834
- container = container.parentNode;
33835
- }
33921
+ base64 = Conversions.parseDataUri(img.src).data;
33922
+ blobInfo = blobCache.findFirst(function(cachedBlobInfo) {
33923
+ return cachedBlobInfo.base64() === base64;
33924
+ });
33836
33925
 
33837
- location = container.getAttribute('data-mce-caret');
33926
+ if (blobInfo) {
33927
+ resolve({
33928
+ image: img,
33929
+ blobInfo: blobInfo
33930
+ });
33931
+ } else {
33932
+ Conversions.uriToBlob(img.src).then(function(blob) {
33933
+ var blobInfoId = 'blobid' + (count++),
33934
+ blobInfo = blobCache.create(blobInfoId, blob, base64);
33838
33935
 
33839
- if (location == 'before') {
33840
- node = container.nextSibling;
33841
- if (isContentEditableFalse(node)) {
33842
- return before(node);
33843
- }
33844
- }
33936
+ blobCache.add(blobInfo);
33845
33937
 
33846
- if (location == 'after') {
33847
- node = container.previousSibling;
33848
- if (isContentEditableFalse(node)) {
33849
- return after(node);
33938
+ resolve({
33939
+ image: img,
33940
+ blobInfo: blobInfo
33941
+ });
33942
+ });
33850
33943
  }
33851
33944
  }
33852
- }
33853
33945
 
33854
- if (!range.collapsed) {
33855
- return range;
33856
- }
33946
+ if (!predicate) {
33947
+ predicate = Fun.constant(true);
33948
+ }
33857
33949
 
33858
- if (NodeType.isText(container)) {
33859
- if (isCaretContainer(container)) {
33860
- if (direction === 1) {
33861
- node = leanRight(container);
33862
- if (node) {
33863
- return before(node);
33864
- }
33950
+ images = Arr.filter(elm.getElementsByTagName('img'), function(img) {
33951
+ var src = img.src;
33865
33952
 
33866
- node = leanLeft(container);
33867
- if (node) {
33868
- return after(node);
33869
- }
33953
+ if (!Env.fileApi) {
33954
+ return false;
33870
33955
  }
33871
33956
 
33872
- if (direction === -1) {
33873
- node = leanLeft(container);
33874
- if (node) {
33875
- return after(node);
33876
- }
33877
-
33878
- node = leanRight(container);
33879
- if (node) {
33880
- return before(node);
33881
- }
33957
+ if (img.hasAttribute('data-mce-bogus')) {
33958
+ return false;
33882
33959
  }
33883
33960
 
33884
- return range;
33885
- }
33886
-
33887
- if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) {
33888
- if (direction === 1) {
33889
- node = leanRight(container);
33890
- if (node) {
33891
- return before(node);
33892
- }
33961
+ if (img.hasAttribute('data-mce-placeholder')) {
33962
+ return false;
33893
33963
  }
33894
33964
 
33895
- return range;
33896
- }
33897
-
33898
- if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) {
33899
- if (direction === -1) {
33900
- node = leanLeft(container);
33901
- if (node) {
33902
- return after(node);
33903
- }
33965
+ if (!src || src == Env.transparentSrc) {
33966
+ return false;
33904
33967
  }
33905
33968
 
33906
- return range;
33907
- }
33969
+ if (src.indexOf('blob:') === 0) {
33970
+ return true;
33971
+ }
33908
33972
 
33909
- if (offset === container.data.length) {
33910
- node = leanRight(container);
33911
- if (node) {
33912
- return before(node);
33973
+ if (src.indexOf('data:') === 0) {
33974
+ return predicate(img);
33913
33975
  }
33914
33976
 
33915
- return range;
33916
- }
33977
+ return false;
33978
+ });
33917
33979
 
33918
- if (offset === 0) {
33919
- node = leanLeft(container);
33920
- if (node) {
33921
- return after(node);
33980
+ promises = Arr.map(images, function(img) {
33981
+ var newPromise;
33982
+
33983
+ if (cachedPromises[img.src]) {
33984
+ // Since the cached promise will return the cached image
33985
+ // We need to wrap it and resolve with the actual image
33986
+ return new Promise(function(resolve) {
33987
+ cachedPromises[img.src].then(function(imageInfo) {
33988
+ resolve({
33989
+ image: img,
33990
+ blobInfo: imageInfo.blobInfo
33991
+ });
33992
+ });
33993
+ });
33922
33994
  }
33923
33995
 
33924
- return range;
33925
- }
33926
- }
33996
+ newPromise = new Promise(function(resolve) {
33997
+ imageToBlobInfo(img, resolve);
33998
+ }).then(function(result) {
33999
+ delete cachedPromises[result.image.src];
34000
+ return result;
34001
+ })['catch'](function(error) {
34002
+ delete cachedPromises[img.src];
34003
+ return error;
34004
+ });
33927
34005
 
33928
- return range;
33929
- }
34006
+ cachedPromises[img.src] = newPromise;
33930
34007
 
33931
- function isNextToContentEditableFalse(relativeOffset, caretPosition) {
33932
- return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition));
33933
- }
34008
+ return newPromise;
34009
+ });
33934
34010
 
33935
- return {
33936
- isForwards: isForwards,
33937
- isBackwards: isBackwards,
33938
- findNode: findNode,
33939
- getEditingHost: getEditingHost,
33940
- getParentBlock: getParentBlock,
33941
- isInSameBlock: isInSameBlock,
33942
- isInSameEditingHost: isInSameEditingHost,
33943
- isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0),
33944
- isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1),
33945
- normalizeRange: normalizeRange
34011
+ return Promise.all(promises);
34012
+ }
34013
+
34014
+ return {
34015
+ findAll: findAll
34016
+ };
33946
34017
  };
33947
34018
  });
33948
34019
 
33949
- // Included from: js/tinymce/classes/caret/CaretWalker.js
34020
+ // Included from: js/tinymce/classes/file/BlobCache.js
33950
34021
 
33951
34022
  /**
33952
- * CaretWalker.js
34023
+ * BlobCache.js
33953
34024
  *
33954
34025
  * Released under LGPL License.
33955
34026
  * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -33959,207 +34030,258 @@ define("tinymce/caret/CaretUtils", [
33959
34030
  */
33960
34031
 
33961
34032
  /**
33962
- * This module contains logic for moving around a virtual caret in logical order within a DOM element.
33963
- *
33964
- * It ignores the most obvious invalid caret locations such as within a script element or within a
33965
- * contentEditable=false element but it will return locations that isn't possible to render visually.
34033
+ * Hold blob info objects where a blob has extra internal information.
33966
34034
  *
33967
34035
  * @private
33968
- * @class tinymce.caret.CaretWalker
33969
- * @example
33970
- * var caretWalker = new CaretWalker(rootElm);
33971
- *
33972
- * var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
33973
- * var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
34036
+ * @class tinymce.file.BlobCache
33974
34037
  */
33975
- define("tinymce/caret/CaretWalker", [
33976
- "tinymce/dom/NodeType",
33977
- "tinymce/caret/CaretCandidate",
33978
- "tinymce/caret/CaretPosition",
33979
- "tinymce/caret/CaretUtils",
34038
+ define("tinymce/file/BlobCache", [
33980
34039
  "tinymce/util/Arr",
33981
34040
  "tinymce/util/Fun"
33982
- ], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) {
33983
- var isContentEditableFalse = NodeType.isContentEditableFalse,
33984
- isText = NodeType.isText,
33985
- isElement = NodeType.isElement,
33986
- isForwards = CaretUtils.isForwards,
33987
- isBackwards = CaretUtils.isBackwards,
33988
- isCaretCandidate = CaretCandidate.isCaretCandidate,
33989
- isAtomic = CaretCandidate.isAtomic,
33990
- isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate;
34041
+ ], function(Arr, Fun) {
34042
+ return function() {
34043
+ var cache = [], constant = Fun.constant;
33991
34044
 
33992
- function getParents(node, rootNode) {
33993
- var parents = [];
34045
+ function create(id, blob, base64) {
34046
+ return {
34047
+ id: constant(id),
34048
+ blob: constant(blob),
34049
+ base64: constant(base64),
34050
+ blobUri: constant(URL.createObjectURL(blob))
34051
+ };
34052
+ }
33994
34053
 
33995
- while (node && node != rootNode) {
33996
- parents.push(node);
33997
- node = node.parentNode;
34054
+ function add(blobInfo) {
34055
+ if (!get(blobInfo.id())) {
34056
+ cache.push(blobInfo);
34057
+ }
33998
34058
  }
33999
34059
 
34000
- return parents;
34001
- }
34060
+ function get(id) {
34061
+ return findFirst(function(cachedBlobInfo) {
34062
+ return cachedBlobInfo.id() === id;
34063
+ });
34064
+ }
34002
34065
 
34003
- function nodeAtIndex(container, offset) {
34004
- if (container.hasChildNodes() && offset < container.childNodes.length) {
34005
- return container.childNodes[offset];
34066
+ function findFirst(predicate) {
34067
+ return Arr.filter(cache, predicate)[0];
34006
34068
  }
34007
34069
 
34008
- return null;
34009
- }
34070
+ function getByUri(blobUri) {
34071
+ return findFirst(function(blobInfo) {
34072
+ return blobInfo.blobUri() == blobUri;
34073
+ });
34074
+ }
34010
34075
 
34011
- function getCaretCandidatePosition(direction, node) {
34012
- if (isForwards(direction)) {
34013
- if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) {
34014
- return CaretPosition.before(node);
34015
- }
34076
+ function destroy() {
34077
+ Arr.each(cache, function(cachedBlobInfo) {
34078
+ URL.revokeObjectURL(cachedBlobInfo.blobUri());
34079
+ });
34016
34080
 
34017
- if (isText(node)) {
34018
- return CaretPosition(node, 0);
34019
- }
34081
+ cache = [];
34020
34082
  }
34021
34083
 
34022
- if (isBackwards(direction)) {
34023
- if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) {
34024
- return CaretPosition.after(node);
34025
- }
34084
+ return {
34085
+ create: create,
34086
+ add: add,
34087
+ get: get,
34088
+ getByUri: getByUri,
34089
+ findFirst: findFirst,
34090
+ destroy: destroy
34091
+ };
34092
+ };
34093
+ });
34026
34094
 
34027
- if (isText(node)) {
34028
- return CaretPosition(node, node.data.length);
34029
- }
34095
+ // Included from: js/tinymce/classes/EditorUpload.js
34096
+
34097
+ /**
34098
+ * EditorUpload.js
34099
+ *
34100
+ * Released under LGPL License.
34101
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
34102
+ *
34103
+ * License: http://www.tinymce.com/license
34104
+ * Contributing: http://www.tinymce.com/contributing
34105
+ */
34106
+
34107
+ /**
34108
+ * Handles image uploads, updates undo stack and patches over various internal functions.
34109
+ *
34110
+ * @private
34111
+ * @class tinymce.EditorUpload
34112
+ */
34113
+ define("tinymce/EditorUpload", [
34114
+ "tinymce/util/Arr",
34115
+ "tinymce/file/Uploader",
34116
+ "tinymce/file/ImageScanner",
34117
+ "tinymce/file/BlobCache"
34118
+ ], function(Arr, Uploader, ImageScanner, BlobCache) {
34119
+ return function(editor) {
34120
+ var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings;
34121
+
34122
+ function aliveGuard(callback) {
34123
+ return function(result) {
34124
+ if (editor.selection) {
34125
+ return callback(result);
34126
+ }
34127
+
34128
+ return [];
34129
+ };
34030
34130
  }
34031
34131
 
34032
- if (isBackwards(direction)) {
34033
- return CaretPosition.after(node);
34132
+ // Replaces strings without regexps to avoid FF regexp to big issue
34133
+ function replaceString(content, search, replace) {
34134
+ var index = 0;
34135
+
34136
+ do {
34137
+ index = content.indexOf(search, index);
34138
+
34139
+ if (index !== -1) {
34140
+ content = content.substring(0, index) + replace + content.substr(index + search.length);
34141
+ index += replace.length - search.length + 1;
34142
+ }
34143
+ } while (index !== -1);
34144
+
34145
+ return content;
34034
34146
  }
34035
34147
 
34036
- return CaretPosition.before(node);
34037
- }
34148
+ function replaceImageUrl(content, targetUrl, replacementUrl) {
34149
+ content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"');
34150
+ content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
34038
34151
 
34039
- function findCaretPosition(direction, startCaretPosition, rootNode) {
34040
- var container, offset, node, nextNode, innerNode,
34041
- rootContentEditableFalseElm, caretPosition;
34152
+ return content;
34153
+ }
34042
34154
 
34043
- if (!isElement(rootNode) || !startCaretPosition) {
34044
- return null;
34155
+ function replaceUrlInUndoStack(targetUrl, replacementUrl) {
34156
+ Arr.each(editor.undoManager.data, function(level) {
34157
+ level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
34158
+ });
34045
34159
  }
34046
34160
 
34047
- caretPosition = startCaretPosition;
34048
- container = caretPosition.container();
34049
- offset = caretPosition.offset();
34161
+ function openNotification() {
34162
+ return editor.notificationManager.open({
34163
+ text: editor.translate('Image uploading...'),
34164
+ type: 'info',
34165
+ timeout: -1,
34166
+ progressBar: true
34167
+ });
34168
+ }
34050
34169
 
34051
- if (isText(container)) {
34052
- if (isBackwards(direction) && offset > 0) {
34053
- return CaretPosition(container, --offset);
34170
+ function uploadImages(callback) {
34171
+ if (!uploader) {
34172
+ uploader = new Uploader({
34173
+ url: settings.images_upload_url,
34174
+ basePath: settings.images_upload_base_path,
34175
+ credentials: settings.images_upload_credentials,
34176
+ handler: settings.images_upload_handler
34177
+ });
34054
34178
  }
34055
34179
 
34056
- if (isForwards(direction) && offset < container.length) {
34057
- return CaretPosition(container, ++offset);
34058
- }
34180
+ return scanForImages().then(aliveGuard(function(imageInfos) {
34181
+ var blobInfos;
34059
34182
 
34060
- node = container;
34061
- } else {
34062
- if (isBackwards(direction) && offset > 0) {
34063
- nextNode = nodeAtIndex(container, offset - 1);
34064
- if (isCaretCandidate(nextNode)) {
34065
- if (!isAtomic(nextNode)) {
34066
- innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
34067
- if (innerNode) {
34068
- if (isText(innerNode)) {
34069
- return CaretPosition(innerNode, innerNode.data.length);
34070
- }
34183
+ blobInfos = Arr.map(imageInfos, function(imageInfo) {
34184
+ return imageInfo.blobInfo;
34185
+ });
34071
34186
 
34072
- return CaretPosition.after(innerNode);
34073
- }
34074
- }
34187
+ return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) {
34188
+ result = Arr.map(result, function(uploadInfo, index) {
34189
+ var image = imageInfos[index].image;
34075
34190
 
34076
- if (isText(nextNode)) {
34077
- return CaretPosition(nextNode, nextNode.data.length);
34078
- }
34191
+ if (uploadInfo.status) {
34192
+ replaceUrlInUndoStack(image.src, uploadInfo.url);
34079
34193
 
34080
- return CaretPosition.before(nextNode);
34081
- }
34082
- }
34194
+ editor.$(image).attr({
34195
+ src: uploadInfo.url,
34196
+ 'data-mce-src': editor.convertURL(uploadInfo.url, 'src')
34197
+ });
34198
+ }
34083
34199
 
34084
- if (isForwards(direction) && offset < container.childNodes.length) {
34085
- nextNode = nodeAtIndex(container, offset);
34086
- if (isCaretCandidate(nextNode)) {
34087
- if (!isAtomic(nextNode)) {
34088
- innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
34089
- if (innerNode) {
34090
- if (isText(innerNode)) {
34091
- return CaretPosition(innerNode, 0);
34092
- }
34200
+ return {
34201
+ element: image,
34202
+ status: uploadInfo.status
34203
+ };
34204
+ });
34093
34205
 
34094
- return CaretPosition.before(innerNode);
34095
- }
34206
+ if (callback) {
34207
+ callback(result);
34096
34208
  }
34097
34209
 
34098
- if (isText(nextNode)) {
34099
- return CaretPosition(nextNode, 0);
34100
- }
34210
+ return result;
34211
+ }));
34212
+ }));
34213
+ }
34101
34214
 
34102
- return CaretPosition.after(nextNode);
34103
- }
34215
+ function uploadImagesAuto(callback) {
34216
+ if (settings.automatic_uploads !== false) {
34217
+ return uploadImages(callback);
34104
34218
  }
34105
-
34106
- node = caretPosition.getNode();
34107
34219
  }
34108
34220
 
34109
- if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) {
34110
- node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true);
34111
- if (isEditableCaretCandidate(node)) {
34112
- return getCaretCandidatePosition(direction, node);
34221
+ function scanForImages() {
34222
+ if (!imageScanner) {
34223
+ imageScanner = new ImageScanner(blobCache);
34113
34224
  }
34114
- }
34115
34225
 
34116
- nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode);
34226
+ return imageScanner.findAll(editor.getBody(), settings.images_dataimg_filter).then(aliveGuard(function(result) {
34227
+ Arr.each(result, function(resultItem) {
34228
+ replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
34229
+ resultItem.image.src = resultItem.blobInfo.blobUri();
34230
+ });
34117
34231
 
34118
- rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse));
34119
- if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
34120
- if (isForwards(direction)) {
34121
- caretPosition = CaretPosition.after(rootContentEditableFalseElm);
34122
- } else {
34123
- caretPosition = CaretPosition.before(rootContentEditableFalseElm);
34124
- }
34232
+ return result;
34233
+ }));
34234
+ }
34125
34235
 
34126
- return caretPosition;
34236
+ function destroy() {
34237
+ blobCache.destroy();
34238
+ imageScanner = uploader = null;
34127
34239
  }
34128
34240
 
34129
- if (nextNode) {
34130
- return getCaretCandidatePosition(direction, nextNode);
34241
+ function replaceBlobWithBase64(content) {
34242
+ return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) {
34243
+ var blobInfo = blobCache.getByUri(blobUri);
34244
+
34245
+ if (!blobInfo) {
34246
+ blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) {
34247
+ return result || editor.editorUpload.blobCache.getByUri(blobUri);
34248
+ }, null);
34249
+ }
34250
+
34251
+ if (blobInfo) {
34252
+ return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"';
34253
+ }
34254
+
34255
+ return match;
34256
+ });
34131
34257
  }
34132
34258
 
34133
- return null;
34134
- }
34259
+ editor.on('setContent', function() {
34260
+ if (editor.settings.automatic_uploads !== false) {
34261
+ uploadImagesAuto();
34262
+ } else {
34263
+ scanForImages();
34264
+ }
34265
+ });
34135
34266
 
34136
- return function(rootNode) {
34137
- return {
34138
- /**
34139
- * Returns the next logical caret position from the specificed input
34140
- * caretPoisiton or null if there isn't any more positions left for example
34141
- * at the end specified root element.
34142
- *
34143
- * @method next
34144
- * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
34145
- * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
34146
- */
34147
- next: function(caretPosition) {
34148
- return findCaretPosition(1, caretPosition, rootNode);
34149
- },
34267
+ editor.on('RawSaveContent', function(e) {
34268
+ e.content = replaceBlobWithBase64(e.content);
34269
+ });
34150
34270
 
34151
- /**
34152
- * Returns the previous logical caret position from the specificed input
34153
- * caretPoisiton or null if there isn't any more positions left for example
34154
- * at the end specified root element.
34155
- *
34156
- * @method prev
34157
- * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
34158
- * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
34159
- */
34160
- prev: function(caretPosition) {
34161
- return findCaretPosition(-1, caretPosition, rootNode);
34271
+ editor.on('getContent', function(e) {
34272
+ if (e.source_view || e.format == 'raw') {
34273
+ return;
34162
34274
  }
34275
+
34276
+ e.content = replaceBlobWithBase64(e.content);
34277
+ });
34278
+
34279
+ return {
34280
+ blobCache: blobCache,
34281
+ uploadImages: uploadImages,
34282
+ uploadImagesAuto: uploadImagesAuto,
34283
+ scanForImages: scanForImages,
34284
+ destroy: destroy
34163
34285
  };
34164
34286
  };
34165
34287
  });
@@ -35877,10 +35999,11 @@ define("tinymce/Editor", [
35877
35999
  * @param {tinymce.EditorManager} editorManager EditorManager instance.
35878
36000
  */
35879
36001
  function Editor(id, settings, editorManager) {
35880
- var self = this, documentBaseUrl, baseUri;
36002
+ var self = this, documentBaseUrl, baseUri, defaultSettings;
35881
36003
 
35882
36004
  documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
35883
36005
  baseUri = editorManager.baseURI;
36006
+ defaultSettings = editorManager.defaultSettings;
35884
36007
 
35885
36008
  /**
35886
36009
  * Name/value collection with editor settings.
@@ -35891,7 +36014,7 @@ define("tinymce/Editor", [
35891
36014
  * // Get the value of the theme setting
35892
36015
  * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
35893
36016
  */
35894
- self.settings = settings = extend({
36017
+ settings = extend({
35895
36018
  id: id,
35896
36019
  theme: 'modern',
35897
36020
  delta_width: 0,
@@ -35929,11 +36052,16 @@ define("tinymce/Editor", [
35929
36052
  url_converter: self.convertURL,
35930
36053
  url_converter_scope: self,
35931
36054
  ie7_compat: true
35932
- }, editorManager.defaultSettings, settings);
36055
+ }, defaultSettings, settings);
36056
+
36057
+ // Merge external_plugins
36058
+ if (defaultSettings && defaultSettings.external_plugins && settings.external_plugins) {
36059
+ settings.external_plugins = extend({}, defaultSettings.external_plugins, settings.external_plugins);
36060
+ }
35933
36061
 
36062
+ self.settings = settings;
35934
36063
  AddOnManager.language = settings.language || 'en';
35935
36064
  AddOnManager.languageLoad = settings.language_load;
35936
-
35937
36065
  AddOnManager.baseURL = editorManager.baseURL;
35938
36066
 
35939
36067
  /**
@@ -38425,16 +38553,20 @@ define("tinymce/EditorManager", [
38425
38553
 
38426
38554
  function globalEventDelegate(e) {
38427
38555
  each(EditorManager.editors, function(editor) {
38428
- editor.fire('ResizeWindow', e);
38556
+ if (e.type === 'scroll') {
38557
+ editor.fire('ScrollWindow', e);
38558
+ } else {
38559
+ editor.fire('ResizeWindow', e);
38560
+ }
38429
38561
  });
38430
38562
  }
38431
38563
 
38432
38564
  function toggleGlobalEvents(editors, state) {
38433
38565
  if (state !== boundGlobalEvents) {
38434
38566
  if (state) {
38435
- $(window).on('resize', globalEventDelegate);
38567
+ $(window).on('resize scroll', globalEventDelegate);
38436
38568
  } else {
38437
- $(window).off('resize', globalEventDelegate);
38569
+ $(window).off('resize scroll', globalEventDelegate);
38438
38570
  }
38439
38571
 
38440
38572
  boundGlobalEvents = state;
@@ -38502,7 +38634,7 @@ define("tinymce/EditorManager", [
38502
38634
  * @property minorVersion
38503
38635
  * @type String
38504
38636
  */
38505
- minorVersion: '3.7',
38637
+ minorVersion: '3.8',
38506
38638
 
38507
38639
  /**
38508
38640
  * Release date of TinyMCE build.
@@ -38510,7 +38642,7 @@ define("tinymce/EditorManager", [
38510
38642
  * @property releaseDate
38511
38643
  * @type String
38512
38644
  */
38513
- releaseDate: '2016-03-02',
38645
+ releaseDate: '2016-03-15',
38514
38646
 
38515
38647
  /**
38516
38648
  * Collection of editor instances.