tinymce-rails 4.3.7 → 4.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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.