wysihtml5x-rails 0.4.16 → 0.4.17

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.
@@ -1,8 +1,84 @@
1
1
  // TODO: in future try to replace most inline compability checks with polyfills for code readability
2
2
 
3
- // element.textContent polyfill.
4
- // Unsupporting browsers: IE8
3
+ // IE8 SUPPORT BLOCK
4
+ // You can compile wuthout all this if IE8 is not needed
5
+
6
+ // addEventListener, removeEventListener
7
+ // TODO: make usage of wysihtml5.dom.observe obsolete
8
+ (function() {
9
+ if (!Event.prototype.preventDefault) {
10
+ Event.prototype.preventDefault=function() {
11
+ this.returnValue=false;
12
+ };
13
+ }
14
+ if (!Event.prototype.stopPropagation) {
15
+ Event.prototype.stopPropagation=function() {
16
+ this.cancelBubble=true;
17
+ };
18
+ }
19
+ if (!Element.prototype.addEventListener) {
20
+ var eventListeners=[];
21
+
22
+ var addEventListener=function(type,listener /*, useCapture (will be ignored) */) {
23
+ var self=this;
24
+ var wrapper=function(e) {
25
+ e.target=e.srcElement;
26
+ e.currentTarget=self;
27
+ if (listener.handleEvent) {
28
+ listener.handleEvent(e);
29
+ } else {
30
+ listener.call(self,e);
31
+ }
32
+ };
33
+ if (type=="DOMContentLoaded") {
34
+ var wrapper2=function(e) {
35
+ if (document.readyState=="complete") {
36
+ wrapper(e);
37
+ }
38
+ };
39
+ document.attachEvent("onreadystatechange",wrapper2);
40
+ eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2});
41
+
42
+ if (document.readyState=="complete") {
43
+ var e=new Event();
44
+ e.srcElement=window;
45
+ wrapper2(e);
46
+ }
47
+ } else {
48
+ this.attachEvent("on"+type,wrapper);
49
+ eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper});
50
+ }
51
+ };
52
+ var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) {
53
+ var counter=0;
54
+ while (counter<eventListeners.length) {
55
+ var eventListener=eventListeners[counter];
56
+ if (eventListener.object==this && eventListener.type==type && eventListener.listener==listener) {
57
+ if (type=="DOMContentLoaded") {
58
+ this.detachEvent("onreadystatechange",eventListener.wrapper);
59
+ } else {
60
+ this.detachEvent("on"+type,eventListener.wrapper);
61
+ }
62
+ eventListeners.splice(counter, 1);
63
+ break;
64
+ }
65
+ ++counter;
66
+ }
67
+ };
68
+ Element.prototype.addEventListener=addEventListener;
69
+ Element.prototype.removeEventListener=removeEventListener;
70
+ if (HTMLDocument) {
71
+ HTMLDocument.prototype.addEventListener=addEventListener;
72
+ HTMLDocument.prototype.removeEventListener=removeEventListener;
73
+ }
74
+ if (Window) {
75
+ Window.prototype.addEventListener=addEventListener;
76
+ Window.prototype.removeEventListener=removeEventListener;
77
+ }
78
+ }
79
+ })();
5
80
 
81
+ // element.textContent polyfill.
6
82
  if (Object.defineProperty && Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(Element.prototype, "textContent") && !Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get) {
7
83
  (function() {
8
84
  var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText");
@@ -24,8 +100,35 @@ if(!Array.isArray) {
24
100
  Array.isArray = function(arg) {
25
101
  return Object.prototype.toString.call(arg) === '[object Array]';
26
102
  };
103
+ }
104
+
105
+ // Function.prototype.bind()
106
+ // TODO: clean the code from variable 'that' as it can be confusing
107
+ if (!Function.prototype.bind) {
108
+ Function.prototype.bind = function(oThis) {
109
+ if (typeof this !== 'function') {
110
+ // closest thing possible to the ECMAScript 5
111
+ // internal IsCallable function
112
+ throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
113
+ }
114
+
115
+ var aArgs = Array.prototype.slice.call(arguments, 1),
116
+ fToBind = this,
117
+ fNOP = function() {},
118
+ fBound = function() {
119
+ return fToBind.apply(this instanceof fNOP && oThis
120
+ ? this
121
+ : oThis,
122
+ aArgs.concat(Array.prototype.slice.call(arguments)));
123
+ };
124
+
125
+ fNOP.prototype = this.prototype;
126
+ fBound.prototype = new fNOP();
127
+
128
+ return fBound;
129
+ };
27
130
  };/**
28
- * @license wysihtml5x v0.4.16
131
+ * @license wysihtml5x v0.4.17
29
132
  * https://github.com/Edicy/wysihtml5
30
133
  *
31
134
  * Author: Christopher Blum (https://github.com/tiff)
@@ -36,7 +139,7 @@ if(!Array.isArray) {
36
139
  *
37
140
  */
38
141
  var wysihtml5 = {
39
- version: "0.4.16",
142
+ version: "0.4.17",
40
143
 
41
144
  // namespaces
42
145
  commands: {},
@@ -48,6 +151,7 @@ var wysihtml5 = {
48
151
  views: {},
49
152
 
50
153
  INVISIBLE_SPACE: "\uFEFF",
154
+ INVISIBLE_SPACE_REG_EXP: /\uFEFF/g,
51
155
 
52
156
  EMPTY_FUNCTION: function() {},
53
157
 
@@ -58,32 +162,29 @@ var wysihtml5 = {
58
162
  ENTER_KEY: 13,
59
163
  ESCAPE_KEY: 27,
60
164
  SPACE_KEY: 32,
165
+ TAB_KEY: 9,
61
166
  DELETE_KEY: 46
62
167
  };
63
168
  ;/**
64
169
  * Rangy, a cross-browser JavaScript range and selection library
65
- * http://code.google.com/p/rangy/
170
+ * https://github.com/timdown/rangy
66
171
  *
67
172
  * Copyright 2014, Tim Down
68
173
  * Licensed under the MIT license.
69
- * Version: 1.3alpha.20140804
70
- * Build date: 4 August 2014
174
+ * Version: 1.3.0-alpha.20140921
175
+ * Build date: 21 September 2014
71
176
  */
72
177
 
73
- (function(factory, global) {
178
+ (function(factory, root) {
74
179
  if (typeof define == "function" && define.amd) {
75
180
  // AMD. Register as an anonymous module.
76
181
  define(factory);
77
- /*
78
- TODO: look into this properly.
79
-
80
- } else if (typeof exports == "object") {
81
- // Node/CommonJS style for Browserify
82
- module.exports = factory;
83
- */
182
+ } else if (typeof module != "undefined" && typeof exports == "object") {
183
+ // Node/CommonJS style
184
+ module.exports = factory();
84
185
  } else {
85
- // No AMD or CommonJS support so we place Rangy in a global variable
86
- global.rangy = factory();
186
+ // No AMD or CommonJS support so we place Rangy in (probably) the global variable
187
+ root.rangy = factory();
87
188
  }
88
189
  })(function() {
89
190
 
@@ -150,24 +251,26 @@ var wysihtml5 = {
150
251
 
151
252
  var modules = {};
152
253
 
254
+ var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED);
255
+
256
+ var util = {
257
+ isHostMethod: isHostMethod,
258
+ isHostObject: isHostObject,
259
+ isHostProperty: isHostProperty,
260
+ areHostMethods: areHostMethods,
261
+ areHostObjects: areHostObjects,
262
+ areHostProperties: areHostProperties,
263
+ isTextRange: isTextRange,
264
+ getBody: getBody
265
+ };
266
+
153
267
  var api = {
154
- version: "1.3alpha.20140804",
268
+ version: "1.3.0-alpha.20140921",
155
269
  initialized: false,
270
+ isBrowser: isBrowser,
156
271
  supported: true,
157
-
158
- util: {
159
- isHostMethod: isHostMethod,
160
- isHostObject: isHostObject,
161
- isHostProperty: isHostProperty,
162
- areHostMethods: areHostMethods,
163
- areHostObjects: areHostObjects,
164
- areHostProperties: areHostProperties,
165
- isTextRange: isTextRange,
166
- getBody: getBody
167
- },
168
-
272
+ util: util,
169
273
  features: {},
170
-
171
274
  modules: modules,
172
275
  config: {
173
276
  alertOnFail: true,
@@ -178,14 +281,14 @@ var wysihtml5 = {
178
281
  };
179
282
 
180
283
  function consoleLog(msg) {
181
- if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
182
- window.console.log(msg);
284
+ if (typeof console != UNDEFINED && isHostMethod(console, "log")) {
285
+ console.log(msg);
183
286
  }
184
287
  }
185
288
 
186
289
  function alertOrLog(msg, shouldAlert) {
187
- if (shouldAlert) {
188
- window.alert(msg);
290
+ if (isBrowser && shouldAlert) {
291
+ alert(msg);
189
292
  } else {
190
293
  consoleLog(msg);
191
294
  }
@@ -194,7 +297,7 @@ var wysihtml5 = {
194
297
  function fail(reason) {
195
298
  api.initialized = true;
196
299
  api.supported = false;
197
- alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail);
300
+ alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail);
198
301
  }
199
302
 
200
303
  api.fail = fail;
@@ -206,15 +309,16 @@ var wysihtml5 = {
206
309
  api.warn = warn;
207
310
 
208
311
  // Add utility extend() method
312
+ var extend;
209
313
  if ({}.hasOwnProperty) {
210
- api.util.extend = function(obj, props, deep) {
314
+ util.extend = extend = function(obj, props, deep) {
211
315
  var o, p;
212
316
  for (var i in props) {
213
317
  if (props.hasOwnProperty(i)) {
214
318
  o = obj[i];
215
319
  p = props[i];
216
320
  if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
217
- api.util.extend(o, p, true);
321
+ extend(o, p, true);
218
322
  }
219
323
  obj[i] = p;
220
324
  }
@@ -225,23 +329,40 @@ var wysihtml5 = {
225
329
  }
226
330
  return obj;
227
331
  };
332
+
333
+ util.createOptions = function(optionsParam, defaults) {
334
+ var options = {};
335
+ extend(options, defaults);
336
+ if (optionsParam) {
337
+ extend(options, optionsParam);
338
+ }
339
+ return options;
340
+ };
228
341
  } else {
229
342
  fail("hasOwnProperty not supported");
230
343
  }
344
+
345
+ // Test whether we're in a browser and bail out if not
346
+ if (!isBrowser) {
347
+ fail("Rangy can only run in a browser");
348
+ }
231
349
 
232
350
  // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
233
351
  (function() {
234
- var el = document.createElement("div");
235
- el.appendChild(document.createElement("span"));
236
- var slice = [].slice;
237
352
  var toArray;
238
- try {
239
- if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
240
- toArray = function(arrayLike) {
241
- return slice.call(arrayLike, 0);
242
- };
243
- }
244
- } catch (e) {}
353
+
354
+ if (isBrowser) {
355
+ var el = document.createElement("div");
356
+ el.appendChild(document.createElement("span"));
357
+ var slice = [].slice;
358
+ try {
359
+ if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
360
+ toArray = function(arrayLike) {
361
+ return slice.call(arrayLike, 0);
362
+ };
363
+ }
364
+ } catch (e) {}
365
+ }
245
366
 
246
367
  if (!toArray) {
247
368
  toArray = function(arrayLike) {
@@ -253,26 +374,27 @@ var wysihtml5 = {
253
374
  };
254
375
  }
255
376
 
256
- api.util.toArray = toArray;
377
+ util.toArray = toArray;
257
378
  })();
258
379
 
259
-
260
380
  // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
261
381
  // normalization of event properties
262
382
  var addListener;
263
- if (isHostMethod(document, "addEventListener")) {
264
- addListener = function(obj, eventType, listener) {
265
- obj.addEventListener(eventType, listener, false);
266
- };
267
- } else if (isHostMethod(document, "attachEvent")) {
268
- addListener = function(obj, eventType, listener) {
269
- obj.attachEvent("on" + eventType, listener);
270
- };
271
- } else {
272
- fail("Document does not have required addEventListener or attachEvent method");
273
- }
383
+ if (isBrowser) {
384
+ if (isHostMethod(document, "addEventListener")) {
385
+ addListener = function(obj, eventType, listener) {
386
+ obj.addEventListener(eventType, listener, false);
387
+ };
388
+ } else if (isHostMethod(document, "attachEvent")) {
389
+ addListener = function(obj, eventType, listener) {
390
+ obj.attachEvent("on" + eventType, listener);
391
+ };
392
+ } else {
393
+ fail("Document does not have required addEventListener or attachEvent method");
394
+ }
274
395
 
275
- api.util.addListener = addListener;
396
+ util.addListener = addListener;
397
+ }
276
398
 
277
399
  var initListeners = [];
278
400
 
@@ -282,7 +404,7 @@ var wysihtml5 = {
282
404
 
283
405
  // Initialization
284
406
  function init() {
285
- if (api.initialized) {
407
+ if (!isBrowser || api.initialized) {
286
408
  return;
287
409
  }
288
410
  var testRange;
@@ -368,7 +490,9 @@ var wysihtml5 = {
368
490
  }
369
491
  }
370
492
 
371
- api.shim = api.createMissingNativeApi = shim;
493
+ if (isBrowser) {
494
+ api.shim = api.createMissingNativeApi = shim;
495
+ }
372
496
 
373
497
  function Module(name, dependencies, initializer) {
374
498
  this.name = name;
@@ -420,7 +544,7 @@ var wysihtml5 = {
420
544
  }
421
545
  };
422
546
 
423
- function createModule(isCore, name, dependencies, initFunc) {
547
+ function createModule(name, dependencies, initFunc) {
424
548
  var newModule = new Module(name, dependencies, function(module) {
425
549
  if (!module.initialized) {
426
550
  module.initialized = true;
@@ -430,10 +554,14 @@ var wysihtml5 = {
430
554
  } catch (ex) {
431
555
  var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
432
556
  consoleLog(errorMessage);
557
+ if (ex.stack) {
558
+ consoleLog(ex.stack);
559
+ }
433
560
  }
434
561
  }
435
562
  });
436
563
  modules[name] = newModule;
564
+ return newModule;
437
565
  }
438
566
 
439
567
  api.createModule = function(name) {
@@ -447,16 +575,16 @@ var wysihtml5 = {
447
575
  dependencies = arguments[1];
448
576
  }
449
577
 
450
- var module = createModule(false, name, dependencies, initFunc);
578
+ var module = createModule(name, dependencies, initFunc);
451
579
 
452
580
  // Initialize the module immediately if the core is already initialized
453
- if (api.initialized) {
581
+ if (api.initialized && api.supported) {
454
582
  module.init();
455
583
  }
456
584
  };
457
585
 
458
586
  api.createCoreModule = function(name, dependencies, initFunc) {
459
- createModule(true, name, dependencies, initFunc);
587
+ createModule(name, dependencies, initFunc);
460
588
  };
461
589
 
462
590
  /*----------------------------------------------------------------------------------------------------------------*/
@@ -472,38 +600,6 @@ var wysihtml5 = {
472
600
 
473
601
  /*----------------------------------------------------------------------------------------------------------------*/
474
602
 
475
- // Wait for document to load before running tests
476
-
477
- var docReady = false;
478
-
479
- var loadHandler = function(e) {
480
- if (!docReady) {
481
- docReady = true;
482
- if (!api.initialized && api.config.autoInitialize) {
483
- init();
484
- }
485
- }
486
- };
487
-
488
- // Test whether we have window and document objects that we will need
489
- if (typeof window == UNDEFINED) {
490
- fail("No window found");
491
- return;
492
- }
493
- if (typeof document == UNDEFINED) {
494
- fail("No document found");
495
- return;
496
- }
497
-
498
- if (isHostMethod(document, "addEventListener")) {
499
- document.addEventListener("DOMContentLoaded", loadHandler, false);
500
- }
501
-
502
- // Add a fallback in case the DOMContentLoaded event isn't supported
503
- addListener(window, "load", loadHandler);
504
-
505
- /*----------------------------------------------------------------------------------------------------------------*/
506
-
507
603
  // DOM utility methods used by Rangy
508
604
  api.createCoreModule("DomUtil", [], function(api, module) {
509
605
  var UNDEF = "undefined";
@@ -2385,7 +2481,7 @@ var wysihtml5 = {
2385
2481
 
2386
2482
  /*--------------------------------------------------------------------------------------------------------*/
2387
2483
 
2388
- // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107.
2484
+ // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107.
2389
2485
 
2390
2486
  var el = document.createElement("div");
2391
2487
  el.innerHTML = "123";
@@ -2743,7 +2839,7 @@ var wysihtml5 = {
2743
2839
  // implementation to use by default.
2744
2840
  if (!api.features.implementsDomRange || api.config.preferTextRange) {
2745
2841
  // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
2746
- var globalObj = (function() { return this; })();
2842
+ var globalObj = (function(f) { return f("return this;")(); })(Function);
2747
2843
  if (typeof globalObj.Range == "undefined") {
2748
2844
  globalObj.Range = WrappedTextRange;
2749
2845
  }
@@ -3279,7 +3375,11 @@ var wysihtml5 = {
3279
3375
  // Clone the native range so that changing the selected range does not affect the selection.
3280
3376
  // This is contrary to the spec but is the only way to achieve consistency between browsers. See
3281
3377
  // issue 80.
3282
- this.nativeSelection.addRange(getNativeRange(range).cloneRange());
3378
+ var clonedNativeRange = getNativeRange(range).cloneRange();
3379
+ try {
3380
+ this.nativeSelection.addRange(clonedNativeRange);
3381
+ } catch (ex) {
3382
+ }
3283
3383
 
3284
3384
  // Check whether adding the range was successful
3285
3385
  this.rangeCount = this.nativeSelection.rangeCount;
@@ -3792,33 +3892,57 @@ var wysihtml5 = {
3792
3892
 
3793
3893
  /*----------------------------------------------------------------------------------------------------------------*/
3794
3894
 
3895
+ // Wait for document to load before initializing
3896
+ var docReady = false;
3897
+
3898
+ var loadHandler = function(e) {
3899
+ if (!docReady) {
3900
+ docReady = true;
3901
+ if (!api.initialized && api.config.autoInitialize) {
3902
+ init();
3903
+ }
3904
+ }
3905
+ };
3906
+
3907
+ if (isBrowser) {
3908
+ // Test whether the document has already been loaded and initialize immediately if so
3909
+ if (document.readyState == "complete") {
3910
+ loadHandler();
3911
+ } else {
3912
+ if (isHostMethod(document, "addEventListener")) {
3913
+ document.addEventListener("DOMContentLoaded", loadHandler, false);
3914
+ }
3915
+
3916
+ // Add a fallback in case the DOMContentLoaded event isn't supported
3917
+ addListener(window, "load", loadHandler);
3918
+ }
3919
+ }
3920
+
3795
3921
  return api;
3796
3922
  }, this);;/**
3797
3923
  * Selection save and restore module for Rangy.
3798
3924
  * Saves and restores user selections using marker invisible elements in the DOM.
3799
3925
  *
3800
3926
  * Part of Rangy, a cross-browser JavaScript range and selection library
3801
- * http://code.google.com/p/rangy/
3927
+ * https://github.com/timdown/rangy
3802
3928
  *
3803
3929
  * Depends on Rangy core.
3804
3930
  *
3805
3931
  * Copyright 2014, Tim Down
3806
3932
  * Licensed under the MIT license.
3807
- * Version: 1.3alpha.20140804
3808
- * Build date: 4 August 2014
3933
+ * Version: 1.3.0-alpha.20140921
3934
+ * Build date: 21 September 2014
3809
3935
  */
3810
- (function(factory, global) {
3936
+ (function(factory, root) {
3811
3937
  if (typeof define == "function" && define.amd) {
3812
3938
  // AMD. Register as an anonymous module with a dependency on Rangy.
3813
- define(["rangy"], factory);
3814
- /*
3815
- } else if (typeof exports == "object") {
3816
- // Node/CommonJS style for Browserify
3817
- module.exports = factory;
3818
- */
3939
+ define(["./rangy-core"], factory);
3940
+ } else if (typeof module != "undefined" && typeof exports == "object") {
3941
+ // Node/CommonJS style
3942
+ module.exports = factory( require("rangy") );
3819
3943
  } else {
3820
- // No AMD or CommonJS support so we use the rangy global variable
3821
- factory(global.rangy);
3944
+ // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
3945
+ factory(root.rangy);
3822
3946
  }
3823
3947
  })(function(rangy) {
3824
3948
  rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
@@ -4302,13 +4426,6 @@ wysihtml5.browser = (function() {
4302
4426
  return "currentStyle" in testElement;
4303
4427
  },
4304
4428
 
4305
- /**
4306
- * Firefox on OSX navigates through history when hitting CMD + Arrow right/left
4307
- */
4308
- hasHistoryIssue: function() {
4309
- return isGecko && navigator.platform.substr(0, 3) === "Mac";
4310
- },
4311
-
4312
4429
  /**
4313
4430
  * Whether the browser inserts a <br> when pressing enter in a contentEditable element
4314
4431
  */
@@ -5911,7 +6028,10 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
5911
6028
  DEFAULT_NODE_NAME = "span",
5912
6029
  WHITE_SPACE_REG_EXP = /\s+/,
5913
6030
  defaultRules = { tags: {}, classes: {} },
5914
- currentRules = {};
6031
+ currentRules = {},
6032
+ blockElements = ["ADDRESS" ,"BLOCKQUOTE" ,"CENTER" ,"DIR" ,"DIV" ,"DL" ,"FIELDSET" ,
6033
+ "FORM", "H1" ,"H2" ,"H3" ,"H4" ,"H5" ,"H6" ,"ISINDEX" ,"MENU",
6034
+ "NOFRAMES", "NOSCRIPT" ,"OL" ,"P" ,"PRE","TABLE", "UL"];
5915
6035
 
5916
6036
  /**
5917
6037
  * Iterates over all childs of the element, recreates them, appends them into a document fragment
@@ -5978,7 +6098,8 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
5978
6098
  i = 0,
5979
6099
  fragment,
5980
6100
  newNode,
5981
- newChild;
6101
+ newChild,
6102
+ nodeDisplay;
5982
6103
 
5983
6104
  // Passes directly elemets with uneditable class
5984
6105
  if (uneditableClass && oldNodeType === 1 && wysihtml5.dom.hasClass(oldNode, uneditableClass)) {
@@ -6005,7 +6126,13 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
6005
6126
  }
6006
6127
  }
6007
6128
 
6008
- if (wysihtml5.dom.getStyle("display").from(oldNode) === "block") {
6129
+ nodeDisplay = wysihtml5.dom.getStyle("display").from(oldNode);
6130
+
6131
+ if (nodeDisplay === '') {
6132
+ // Handle display style when element not in dom
6133
+ nodeDisplay = wysihtml5.lang.array(blockElements).contains(oldNode.tagName) ? "block" : "";
6134
+ }
6135
+ if (wysihtml5.lang.array(["block", "flex", "table"]).contains(nodeDisplay)) {
6009
6136
  fragment.appendChild(oldNode.ownerDocument.createElement("br"));
6010
6137
  }
6011
6138
 
@@ -6481,15 +6608,14 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
6481
6608
  }
6482
6609
  }
6483
6610
 
6484
- var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
6485
6611
  function _handleText(oldNode) {
6486
6612
  var nextSibling = oldNode.nextSibling;
6487
6613
  if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
6488
6614
  // Concatenate text nodes
6489
- nextSibling.data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(INVISIBLE_SPACE_REG_EXP, "");
6615
+ nextSibling.data = oldNode.data.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
6490
6616
  } else {
6491
6617
  // \uFEFF = wysihtml5.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
6492
- var data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "");
6618
+ var data = oldNode.data.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
6493
6619
  return oldNode.ownerDocument.createTextNode(data);
6494
6620
  }
6495
6621
  }
@@ -8832,6 +8958,71 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8832
8958
  return this.setSelection(range);
8833
8959
  },
8834
8960
 
8961
+ // Constructs a self removing whitespace (ain absolute positioned span) for placing selection caret when normal methods fail.
8962
+ // Webkit has an issue with placing caret into places where there are no textnodes near by.
8963
+ creteTemporaryCaretSpaceAfter: function (node) {
8964
+ var caretPlaceholder = this.doc.createElement('span'),
8965
+ caretPlaceholderText = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE),
8966
+ placeholderRemover = (function(event) {
8967
+ // Self-destructs the caret and keeps the text inserted into it by user
8968
+ var lastChild;
8969
+
8970
+ this.contain.removeEventListener('mouseup', placeholderRemover);
8971
+ this.contain.removeEventListener('keydown', keyDownHandler);
8972
+ this.contain.removeEventListener('touchstart', placeholderRemover);
8973
+ this.contain.removeEventListener('focus', placeholderRemover);
8974
+ this.contain.removeEventListener('blur', placeholderRemover);
8975
+ this.contain.removeEventListener('paste', delayedPlaceholderRemover);
8976
+ this.contain.removeEventListener('drop', delayedPlaceholderRemover);
8977
+ this.contain.removeEventListener('beforepaste', delayedPlaceholderRemover);
8978
+
8979
+ // If user inserted sth it is in the placeholder and sgould be unwrapped and stripped of invisible whitespace hack
8980
+ // Otherwise the wrapper can just be removed
8981
+ if (caretPlaceholder && caretPlaceholder.parentNode) {
8982
+ caretPlaceholder.innerHTML = caretPlaceholder.innerHTML.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
8983
+ if ((/[^\s]+/).test(caretPlaceholder.innerHTML)) {
8984
+ lastChild = caretPlaceholder.lastChild;
8985
+ wysihtml5.dom.unwrap(caretPlaceholder);
8986
+ this.setAfter(lastChild);
8987
+ } else {
8988
+ caretPlaceholder.parentNode.removeChild(caretPlaceholder);
8989
+ }
8990
+
8991
+ }
8992
+ }).bind(this),
8993
+ delayedPlaceholderRemover = function (event) {
8994
+ if (caretPlaceholder && caretPlaceholder.parentNode) {
8995
+ setTimeout(placeholderRemover, 0);
8996
+ }
8997
+ },
8998
+ keyDownHandler = function(event) {
8999
+ if (event.which !== 8 && event.which !== 91 && event.which !== 17 && (event.which !== 86 || (!event.ctrlKey && !event.metaKey))) {
9000
+ placeholderRemover();
9001
+ }
9002
+ };
9003
+
9004
+ caretPlaceholder.style.position = 'absolute';
9005
+ caretPlaceholder.style.display = 'block';
9006
+ caretPlaceholder.style.minWidth = '1px';
9007
+ caretPlaceholder.style.zIndex = '99999';
9008
+ caretPlaceholder.appendChild(caretPlaceholderText);
9009
+
9010
+ node.parentNode.insertBefore(caretPlaceholder, node.nextSibling);
9011
+ this.setBefore(caretPlaceholderText);
9012
+
9013
+ // Remove the caret fix on any of the following events (some are delayed as content change happens after event)
9014
+ this.contain.addEventListener('mouseup', placeholderRemover);
9015
+ this.contain.addEventListener('keydown', keyDownHandler);
9016
+ this.contain.addEventListener('touchstart', placeholderRemover);
9017
+ this.contain.addEventListener('focus', placeholderRemover);
9018
+ this.contain.addEventListener('blur', placeholderRemover);
9019
+ this.contain.addEventListener('paste', delayedPlaceholderRemover);
9020
+ this.contain.addEventListener('drop', delayedPlaceholderRemover);
9021
+ this.contain.addEventListener('beforepaste', delayedPlaceholderRemover);
9022
+
9023
+ return caretPlaceholder;
9024
+ },
9025
+
8835
9026
  /**
8836
9027
  * Set the caret after the given node
8837
9028
  *
@@ -8840,11 +9031,23 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8840
9031
  * selection.setBefore(myElement);
8841
9032
  */
8842
9033
  setAfter: function(node) {
8843
- var range = rangy.createRange(this.doc);
9034
+ var range = rangy.createRange(this.doc),
9035
+ originalScrollTop = this.doc.documentElement.scrollTop || this.doc.body.scrollTop || this.doc.defaultView.pageYOffset,
9036
+ originalScrollLeft = this.doc.documentElement.scrollLeft || this.doc.body.scrollLeft || this.doc.defaultView.pageXOffset,
9037
+ sel;
8844
9038
 
8845
9039
  range.setStartAfter(node);
8846
9040
  range.setEndAfter(node);
8847
- return this.setSelection(range);
9041
+ this.composer.element.focus();
9042
+ this.doc.defaultView.scrollTo(originalScrollLeft, originalScrollTop);
9043
+ sel = this.setSelection(range);
9044
+
9045
+ // Webkit fails to add selection if there are no textnodes in that region
9046
+ // (like an uneditable container at the end of content).
9047
+ if (!sel) {
9048
+ this.creteTemporaryCaretSpaceAfter(node);
9049
+ }
9050
+ return sel;
8848
9051
  },
8849
9052
 
8850
9053
  /**
@@ -8954,10 +9157,11 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8954
9157
  return false;
8955
9158
  },
8956
9159
 
8957
- // deletes selection contents making sure uneditables/unselectables are not partially deleted
9160
+ // Deletes selection contents making sure uneditables/unselectables are not partially deleted
9161
+ // Triggers wysihtml5:uneditable:delete custom event on all deleted uneditables if customevents suppoorted
8958
9162
  deleteContents: function() {
8959
9163
  var range = this.getRange(),
8960
- startParent, endParent;
9164
+ startParent, endParent, uneditables, ev;
8961
9165
 
8962
9166
  if (this.unselectableClass) {
8963
9167
  if ((startParent = wysihtml5.dom.getParentElement(range.startContainer, { className: this.unselectableClass }, false, this.contain))) {
@@ -8966,6 +9170,18 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8966
9170
  if ((endParent = wysihtml5.dom.getParentElement(range.endContainer, { className: this.unselectableClass }, false, this.contain))) {
8967
9171
  range.setEndAfter(endParent);
8968
9172
  }
9173
+
9174
+ // If customevents present notify uneditable elements of being deleted
9175
+ uneditables = range.getNodes([1], (function (node) {
9176
+ return wysihtml5.dom.hasClass(node, this.unselectableClass);
9177
+ }).bind(this));
9178
+ for (var i = uneditables.length; i--;) {
9179
+ try {
9180
+ ev = new CustomEvent("wysihtml5:uneditable:delete");
9181
+ uneditables[i].dispatchEvent(ev);
9182
+ } catch (err) {}
9183
+ }
9184
+
8969
9185
  }
8970
9186
  range.deleteContents();
8971
9187
  this.setSelection(range);
@@ -9385,6 +9601,24 @@ wysihtml5.quirks.ensureProperClearing = (function() {
9385
9601
  selection.modify("extend", "right", "lineboundary");
9386
9602
  },
9387
9603
 
9604
+ // collapses selection to current line beginning or end
9605
+ toLineBoundary: function (location, collapse) {
9606
+ collapse = (typeof collapse === 'undefined') ? false : collapse;
9607
+ if (wysihtml5.browser.supportsSelectionModify()) {
9608
+ var win = this.doc.defaultView,
9609
+ selection = win.getSelection();
9610
+
9611
+ selection.modify("extend", location, "lineboundary");
9612
+ if (collapse) {
9613
+ if (location === "left") {
9614
+ selection.collapseToStart();
9615
+ } else if (location === "right") {
9616
+ selection.collapseToEnd();
9617
+ }
9618
+ }
9619
+ }
9620
+ },
9621
+
9388
9622
  _selectLine_MSIE: function() {
9389
9623
  var range = this.doc.selection.createRange(),
9390
9624
  rangeTop = range.boundingTop,
@@ -9550,10 +9784,14 @@ wysihtml5.quirks.ensureProperClearing = (function() {
9550
9784
  return rangy.getSelection(this.doc.defaultView || this.doc.parentWindow);
9551
9785
  },
9552
9786
 
9787
+ // Sets selection in document to a given range
9788
+ // Set selection method detects if it fails to set any selection in document and returns null on fail
9789
+ // (especially needed in webkit where some ranges just can not create selection for no reason)
9553
9790
  setSelection: function(range) {
9554
9791
  var win = this.doc.defaultView || this.doc.parentWindow,
9555
9792
  selection = rangy.getSelection(win);
9556
- return selection.setSingleRange(range);
9793
+ selection.setSingleRange(range);
9794
+ return (selection && selection.anchorNode && selection.focusNode) ? selection : null;
9557
9795
  },
9558
9796
 
9559
9797
  createRange: function() {
@@ -10304,6 +10542,13 @@ wysihtml5.Commands = Base.extend(
10304
10542
  method = obj && obj.exec,
10305
10543
  result = null;
10306
10544
 
10545
+ // If composer ahs placeholder unset it before command
10546
+ // Do not apply on commands that are behavioral
10547
+ if (this.composer.hasPlaceholderSet() && !wysihtml5.lang.array(['styleWithCSS', 'enableObjectResizing', 'enableInlineTableEditing']).contains(command)) {
10548
+ this.composer.element.innerHTML = "";
10549
+ this.composer.selection.selectNode(this.composer.element);
10550
+ }
10551
+
10307
10552
  this.editor.fire("beforecommand:composer");
10308
10553
 
10309
10554
  if (method) {
@@ -11412,7 +11657,7 @@ wysihtml5.commands.formatCode = {
11412
11657
 
11413
11658
  // This space causes new lists to never break on enter
11414
11659
  var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
11415
- tempElement.innerHTML = tempElement.innerHTML.replace(INVISIBLE_SPACE_REG_EXP, "");
11660
+ tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
11416
11661
 
11417
11662
  if (tempElement) {
11418
11663
  isEmpty = wysihtml5.lang.array(["", "<br>", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML);
@@ -12777,6 +13022,22 @@ wysihtml5.views.View = Base.extend(
12777
13022
  "85": "underline" // U
12778
13023
  };
12779
13024
 
13025
+ // Adds multiple eventlisteners to target, bound to one callback
13026
+ // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
13027
+ var addListeners = function (target, events, callback) {
13028
+ for(var i = 0, max = events.length; i < max; i++) {
13029
+ target.addEventListener(events[i], callback, false);
13030
+ }
13031
+ };
13032
+
13033
+ // Removes multiple eventlisteners from target, bound to one callback
13034
+ // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
13035
+ var removeListeners = function (target, events, callback) {
13036
+ for(var i = 0, max = events.length; i < max; i++) {
13037
+ target.removeEventListener(events[i], callback, false);
13038
+ }
13039
+ };
13040
+
12780
13041
  var deleteAroundEditable = function(selection, uneditable, element) {
12781
13042
  // merge node with previous node from uneditable
12782
13043
  var prevNode = selection.getPreviousNode(uneditable, true),
@@ -12811,7 +13072,10 @@ wysihtml5.views.View = Base.extend(
12811
13072
  }
12812
13073
  };
12813
13074
 
12814
- var handleDeleteKeyPress = function(event, selection, element, composer) {
13075
+ var handleDeleteKeyPress = function(event, composer) {
13076
+ var selection = composer.selection,
13077
+ element = composer.element;
13078
+
12815
13079
  if (selection.isCollapsed()) {
12816
13080
  if (selection.caretIsInTheBeginnig('LI')) {
12817
13081
  event.preventDefault();
@@ -12870,248 +13134,258 @@ wysihtml5.views.View = Base.extend(
12870
13134
  composer.commands.exec("insertHTML", "&emsp;");
12871
13135
  };
12872
13136
 
12873
- wysihtml5.views.Composer.prototype.observe = function() {
12874
- var that = this,
12875
- state = this.getValue(false, false),
12876
- container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
12877
- element = this.element,
12878
- focusBlurElement = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? element : this.sandbox.getWindow(),
12879
- pasteEvents = ["drop", "paste", "beforepaste"],
12880
- interactionEvents = ["drop", "paste", "mouseup", "focus", "keyup"];
12881
-
12882
- // --------- destroy:composer event ---------
12883
- dom.observe(container, "DOMNodeRemoved", function() {
12884
- clearInterval(domNodeRemovedInterval);
12885
- that.parent.fire("destroy:composer");
12886
- });
12887
-
12888
- // DOMNodeRemoved event is not supported in IE 8
12889
- if (!browser.supportsMutationEvents()) {
12890
- var domNodeRemovedInterval = setInterval(function() {
12891
- if (!dom.contains(document.documentElement, container)) {
12892
- clearInterval(domNodeRemovedInterval);
12893
- that.parent.fire("destroy:composer");
12894
- }
12895
- }, 250);
12896
- }
13137
+ var handleDomNodeRemoved = function(event) {
13138
+ if (this.domNodeRemovedInterval) {
13139
+ clearInterval(domNodeRemovedInterval);
13140
+ }
13141
+ this.parent.fire("destroy:composer");
13142
+ };
12897
13143
 
12898
- // --------- User interaction tracking --
13144
+ // Listens to "drop", "paste", "mouseup", "focus", "keyup" events and fires
13145
+ var handleUserInteraction = function (event) {
13146
+ this.parent.fire("beforeinteraction").fire("beforeinteraction:composer");
13147
+ setTimeout((function() {
13148
+ this.parent.fire("interaction").fire("interaction:composer");
13149
+ }).bind(this), 0);
13150
+ };
12899
13151
 
12900
- dom.observe(focusBlurElement, interactionEvents, function() {
12901
- setTimeout(function() {
12902
- that.parent.fire("interaction").fire("interaction:composer");
12903
- }, 0);
12904
- });
13152
+ var handleFocus = function(event) {
13153
+ this.parent.fire("focus", event).fire("focus:composer", event);
12905
13154
 
13155
+ // Delay storing of state until all focus handler are fired
13156
+ // especially the one which resets the placeholder
13157
+ setTimeout((function() {
13158
+ this.focusState = this.getValue(false, false);
13159
+ }).bind(this), 0);
13160
+ };
12906
13161
 
12907
- if (this.config.handleTables) {
12908
- if(!this.tableClickHandle && this.doc.execCommand && wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") && wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing")) {
12909
- if (this.sandbox.getIframe) {
12910
- this.tableClickHandle = dom.observe(container , ["focus", "mouseup", "mouseover"], function() {
12911
- that.doc.execCommand("enableObjectResizing", false, "false");
12912
- that.doc.execCommand("enableInlineTableEditing", false, "false");
12913
- that.tableClickHandle.stop();
12914
- });
12915
- } else {
12916
- setTimeout(function() {
12917
- that.doc.execCommand("enableObjectResizing", false, "false");
12918
- that.doc.execCommand("enableInlineTableEditing", false, "false");
12919
- }, 0);
12920
- }
13162
+ var handleBlur = function(event) {
13163
+ if (this.focusState !== this.getValue(false, false)) {
13164
+ //create change event if supported (all except IE8)
13165
+ var changeevent = event;
13166
+ if(typeof Object.create == 'function') {
13167
+ changeevent = Object.create(event, { type: { value: 'change' } });
12921
13168
  }
12922
- this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
13169
+ this.parent.fire("change", changeevent).fire("change:composer", changeevent);
12923
13170
  }
13171
+ this.parent.fire("blur", event).fire("blur:composer", event);
13172
+ };
12924
13173
 
12925
- // --------- Focus & blur logic ---------
12926
- dom.observe(focusBlurElement, "focus", function(event) {
12927
- that.parent.fire("focus", event).fire("focus:composer", event);
12928
-
12929
- // Delay storing of state until all focus handler are fired
12930
- // especially the one which resets the placeholder
12931
- setTimeout(function() { state = that.getValue(false, false); }, 0);
12932
- });
12933
-
12934
- dom.observe(focusBlurElement, "blur", function(event) {
12935
- if (state !== that.getValue(false, false)) {
12936
- //create change event if supported (all except IE8)
12937
- var changeevent = event;
12938
- if(typeof Object.create == 'function') {
12939
- changeevent = Object.create(event, { type: { value: 'change' } });
12940
- }
12941
- that.parent.fire("change", changeevent).fire("change:composer", changeevent);
12942
- }
12943
- that.parent.fire("blur", event).fire("blur:composer", event);
12944
- });
12945
-
12946
- // --------- Drag & Drop logic ---------
12947
- dom.observe(element, "dragenter", function() {
12948
- that.parent.fire("unset_placeholder");
12949
- });
12950
-
12951
- dom.observe(element, pasteEvents, function(event) {
12952
- that.parent.fire(event.type, event).fire(event.type + ":composer", event);
12953
- });
12954
-
13174
+ var handlePaste = function(event) {
13175
+ this.parent.fire(event.type, event).fire(event.type + ":composer", event);
13176
+ if (event.type === "paste") {
13177
+ setTimeout((function() {
13178
+ this.parent.fire("newword:composer");
13179
+ }).bind(this), 0);
13180
+ }
13181
+ };
12955
13182
 
13183
+ var handleCopy = function(event) {
12956
13184
  if (this.config.copyedFromMarking) {
12957
- // If supported the copied source is based directly on selection
13185
+ // If supported the copied source can be based directly on selection
12958
13186
  // Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection.
12959
- dom.observe(element, "copy", function(event) {
12960
- if (event.clipboardData) {
12961
- event.clipboardData.setData("text/html", that.config.copyedFromMarking + that.selection.getHtml());
12962
- event.clipboardData.setData("text/plain", that.selection.getPlainText());
12963
- event.preventDefault();
12964
- }
12965
- that.parent.fire(event.type, event).fire(event.type + ":composer", event);
12966
- });
12967
- }
12968
-
12969
- // --------- neword event ---------
12970
- dom.observe(element, "keyup", function(event) {
12971
- var keyCode = event.keyCode;
12972
- if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) {
12973
- that.parent.fire("newword:composer");
13187
+ if (event.clipboardData) {
13188
+ event.clipboardData.setData("text/html", this.config.copyedFromMarking + this.selection.getHtml());
13189
+ event.clipboardData.setData("text/plain", this.selection.getPlainText());
13190
+ event.preventDefault();
12974
13191
  }
12975
- });
13192
+ this.parent.fire(event.type, event).fire(event.type + ":composer", event);
13193
+ }
13194
+ };
12976
13195
 
12977
- this.parent.on("paste:composer", function() {
12978
- setTimeout(function() { that.parent.fire("newword:composer"); }, 0);
12979
- });
13196
+ var handleKeyUp = function(event) {
13197
+ var keyCode = event.keyCode;
13198
+ if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) {
13199
+ this.parent.fire("newword:composer");
13200
+ }
13201
+ };
12980
13202
 
12981
- // --------- Make sure that images are selected when clicking on them ---------
13203
+ var handleMouseDown = function(event) {
12982
13204
  if (!browser.canSelectImagesInContentEditable()) {
12983
- dom.observe(element, "mousedown", function(event) {
12984
- var target = event.target;
12985
- var allImages = element.querySelectorAll('img'),
12986
- notMyImages = element.querySelectorAll('.' + that.config.uneditableContainerClassname + ' img'),
12987
- myImages = wysihtml5.lang.array(allImages).without(notMyImages);
13205
+ // Make sure that images are selected when clicking on them
13206
+ var target = event.target,
13207
+ allImages = this.element.querySelectorAll('img'),
13208
+ notMyImages = this.element.querySelectorAll('.' + this.config.uneditableContainerClassname + ' img'),
13209
+ myImages = wysihtml5.lang.array(allImages).without(notMyImages);
12988
13210
 
12989
- if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
12990
- that.selection.selectNode(target);
12991
- }
12992
- });
13211
+ if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
13212
+ this.selection.selectNode(target);
13213
+ }
13214
+ }
13215
+ };
13216
+
13217
+ // TODO: mouseover is not actually a foolproof and obvious place for this, must be changed as it modifies dom on random basis
13218
+ // Shows url in tooltip when hovering links or images
13219
+ var handleMouseOver = function(event) {
13220
+ var titlePrefixes = {
13221
+ IMG: "Image: ",
13222
+ A: "Link: "
13223
+ },
13224
+ target = event.target,
13225
+ nodeName = target.nodeName,
13226
+ title;
13227
+
13228
+ if (nodeName !== "A" && nodeName !== "IMG") {
13229
+ return;
12993
13230
  }
13231
+ if(!target.hasAttribute("title")){
13232
+ title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
13233
+ target.setAttribute("title", title);
13234
+ }
13235
+ };
12994
13236
 
12995
- // If uneditables configured makes click on uneditable moves caret after clicked element (so it can be deleted like text)
12996
- // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour
13237
+ var handleClick = function(event) {
12997
13238
  if (this.config.uneditableContainerClassname) {
12998
- dom.observe(element, "click", function(event) {
12999
- var uneditable = wysihtml5.dom.getParentElement(event.target, { className: that.config.uneditableContainerClassname }, false, that.element);
13000
- if (uneditable) {
13001
- that.selection.setAfter(uneditable);
13002
- }
13003
- });
13239
+ // If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text)
13240
+ // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour
13241
+ var uneditable = wysihtml5.dom.getParentElement(event.target, { className: this.config.uneditableContainerClassname }, false, this.element);
13242
+ if (uneditable) {
13243
+ this.selection.setAfter(uneditable);
13244
+ }
13004
13245
  }
13246
+ };
13005
13247
 
13248
+ var handleDrop = function(event) {
13006
13249
  if (!browser.canSelectImagesInContentEditable()) {
13007
- dom.observe(element, "drop", function(event) {
13008
- // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
13009
- setTimeout(function() {
13010
- that.selection.getSelection().removeAllRanges();
13011
- }, 0);
13012
- });
13250
+ // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
13251
+ setTimeout((function() {
13252
+ this.selection.getSelection().removeAllRanges();
13253
+ }).bind(this), 0);
13013
13254
  }
13255
+ };
13014
13256
 
13015
- if (browser.hasHistoryIssue() && browser.supportsSelectionModify()) {
13016
- dom.observe(element, "keydown", function(event) {
13017
- if (!event.metaKey && !event.ctrlKey) {
13018
- return;
13019
- }
13257
+ var handleKeyDown = function(event) {
13258
+ var keyCode = event.keyCode,
13259
+ command = shortcuts[keyCode],
13260
+ target, parent;
13020
13261
 
13021
- var keyCode = event.keyCode,
13022
- win = element.ownerDocument.defaultView,
13023
- selection = win.getSelection();
13262
+ // Shortcut logic
13263
+ if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
13264
+ this.commands.exec(command);
13265
+ event.preventDefault();
13266
+ }
13024
13267
 
13025
- if (keyCode === 37 || keyCode === 39) {
13026
- if (keyCode === 37) {
13027
- selection.modify("extend", "left", "lineboundary");
13028
- if (!event.shiftKey) {
13029
- selection.collapseToStart();
13030
- }
13031
- }
13032
- if (keyCode === 39) {
13033
- selection.modify("extend", "right", "lineboundary");
13034
- if (!event.shiftKey) {
13035
- selection.collapseToEnd();
13036
- }
13037
- }
13038
- event.preventDefault();
13039
- }
13040
- });
13268
+ if (keyCode === wysihtml5.BACKSPACE_KEY) {
13269
+ // Delete key override for special cases
13270
+ handleDeleteKeyPress(event, this);
13041
13271
  }
13042
13272
 
13043
- // --------- Shortcut logic ---------
13044
- dom.observe(element, "keydown", function(event) {
13045
- var keyCode = event.keyCode,
13046
- command = shortcuts[keyCode];
13047
- if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
13048
- that.commands.exec(command);
13049
- event.preventDefault();
13050
- }
13051
- if (keyCode === 8) {
13052
- // delete key
13053
- handleDeleteKeyPress(event, that.selection, element, that);
13054
- } else if (that.config.handleTabKey && keyCode === 9) {
13273
+ // Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor
13274
+ if (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY) {
13275
+ target = this.selection.getSelectedNode(true);
13276
+ if (target && target.nodeName === "IMG") {
13055
13277
  event.preventDefault();
13056
- handleTabKeyDown(that, element);
13057
- }
13058
- });
13059
-
13060
- // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
13061
- dom.observe(element, "keydown", function(event) {
13062
- var target = that.selection.getSelectedNode(true),
13063
- keyCode = event.keyCode,
13064
- parent;
13065
- if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete
13066
13278
  parent = target.parentNode;
13067
- // delete the <img>
13068
- parent.removeChild(target);
13069
- // and it's parent <a> too if it hasn't got any other child nodes
13279
+ parent.removeChild(target);// delete the <img>
13280
+ // And it's parent <a> too if it hasn't got any other child nodes
13070
13281
  if (parent.nodeName === "A" && !parent.firstChild) {
13071
13282
  parent.parentNode.removeChild(parent);
13072
13283
  }
13073
-
13074
- setTimeout(function() { wysihtml5.quirks.redraw(element); }, 0);
13075
- event.preventDefault();
13076
- }
13077
- });
13078
-
13079
- // --------- IE 8+9 focus the editor when the iframe is clicked (without actually firing the 'focus' event on the <body>) ---------
13080
- if (!this.config.contentEditableMode && browser.hasIframeFocusIssue()) {
13081
- dom.observe(container, "focus", function() {
13082
13284
  setTimeout(function() {
13083
- if (that.doc.querySelector(":focus") !== that.element) {
13084
- that.focus();
13085
- }
13285
+ wysihtml5.quirks.redraw(element);
13086
13286
  }, 0);
13087
- });
13287
+ }
13288
+ }
13088
13289
 
13089
- dom.observe(this.element, "blur", function() {
13090
- setTimeout(function() {
13091
- that.selection.getSelection().removeAllRanges();
13092
- }, 0);
13093
- });
13290
+ if (this.config.handleTabKey && keyCode === wysihtml5.TAB_KEY) {
13291
+ // TAB key handling
13292
+ event.preventDefault();
13293
+ handleTabKeyDown(this, element);
13094
13294
  }
13095
13295
 
13096
- // --------- Show url in tooltip when hovering links or images ---------
13097
- var titlePrefixes = {
13098
- IMG: "Image: ",
13099
- A: "Link: "
13100
- };
13296
+ };
13101
13297
 
13102
- dom.observe(element, "mouseover", function(event) {
13103
- var target = event.target,
13104
- nodeName = target.nodeName,
13105
- title;
13106
- if (nodeName !== "A" && nodeName !== "IMG") {
13107
- return;
13298
+ var handleIframeFocus = function(event) {
13299
+ setTimeout((function() {
13300
+ if (this.doc.querySelector(":focus") !== this.element) {
13301
+ this.focus();
13108
13302
  }
13109
- var hasTitle = target.hasAttribute("title");
13110
- if(!hasTitle){
13111
- title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
13112
- target.setAttribute("title", title);
13303
+ }).bind(this), 0);
13304
+ };
13305
+
13306
+ var handleIframeBlur = function(event) {
13307
+ setTimeout((function() {
13308
+ this.selection.getSelection().removeAllRanges();
13309
+ }).bind(this), 0);
13310
+ };
13311
+
13312
+ // Table management
13313
+ // If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
13314
+ var initTableHandling = function () {
13315
+ var hideHandlers = function () {
13316
+ this.doc.execCommand("enableObjectResizing", false, "false");
13317
+ this.doc.execCommand("enableInlineTableEditing", false, "false");
13318
+ },
13319
+ iframeInitiator = (function() {
13320
+ hideHandlers.call(this);
13321
+ removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
13322
+ }).bind(this);
13323
+
13324
+ if( this.doc.execCommand &&
13325
+ wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") &&
13326
+ wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing"))
13327
+ {
13328
+ if (this.sandbox.getIframe) {
13329
+ addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
13330
+ } else {
13331
+ setTimeout((function() {
13332
+ hideHandlers.call(this);
13333
+ }).bind(this), 0);
13113
13334
  }
13114
- });
13335
+ }
13336
+ this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
13337
+ };
13338
+
13339
+ wysihtml5.views.Composer.prototype.observe = function() {
13340
+ var that = this,
13341
+ container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
13342
+ element = this.element,
13343
+ focusBlurElement = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? this.element : this.sandbox.getWindow();
13344
+
13345
+ this.focusState = this.getValue(false, false);
13346
+
13347
+ // --------- destroy:composer event ---------
13348
+ container.addEventListener(["DOMNodeRemoved"], handleDomNodeRemoved.bind(this), false);
13349
+
13350
+ // DOMNodeRemoved event is not supported in IE 8
13351
+ // TODO: try to figure out a polyfill style fix, so it could be transferred to polyfills and removed if ie8 is not needed
13352
+ if (!browser.supportsMutationEvents()) {
13353
+ this.domNodeRemovedInterval = setInterval(function() {
13354
+ if (!dom.contains(document.documentElement, container)) {
13355
+ handleDomNodeRemoved.call(this);
13356
+ }
13357
+ }, 250);
13358
+ }
13359
+
13360
+ // --------- User interactions --
13361
+ if (this.config.handleTables) {
13362
+ // If handleTables option is true, table handling functions are bound
13363
+ initTableHandling.call(this);
13364
+ }
13365
+
13366
+ addListeners(focusBlurElement, ["drop", "paste", "mouseup", "focus", "keyup"], handleUserInteraction.bind(this));
13367
+ focusBlurElement.addEventListener("focus", handleFocus.bind(this), false);
13368
+ focusBlurElement.addEventListener("blur", handleBlur.bind(this), false);
13369
+
13370
+ addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
13371
+ this.element.addEventListener("copy", handleCopy.bind(this), false);
13372
+ this.element.addEventListener("mousedown", handleMouseDown.bind(this), false);
13373
+ this.element.addEventListener("mouseover", handleMouseOver.bind(this), false);
13374
+ this.element.addEventListener("click", handleClick.bind(this), false);
13375
+ this.element.addEventListener("drop", handleDrop.bind(this), false);
13376
+ this.element.addEventListener("keyup", handleKeyUp.bind(this), false);
13377
+ this.element.addEventListener("keydown", handleKeyDown.bind(this), false);
13378
+
13379
+ this.element.addEventListener("dragenter", (function() {
13380
+ this.parent.fire("unset_placeholder");
13381
+ }).bind(this), false);
13382
+
13383
+ // --------- IE 8+9 focus the editor when the iframe is clicked (without actually firing the 'focus' event on the <body>) ---------
13384
+ if (!this.config.contentEditableMode && browser.hasIframeFocusIssue()) {
13385
+ container.addEventListener("focus", handleIframeFocus.bind(this), false);
13386
+ container.addEventListener("blur", handleIframeBlur.bind(this), false);
13387
+ }
13388
+
13115
13389
  };
13116
13390
  })(wysihtml5);
13117
13391
  ;/**