bootstrap-wysihtml5-rails 0.2.4 → 0.2.5

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.
data/Rakefile CHANGED
@@ -18,4 +18,5 @@ end
18
18
  desc "Publish the gem"
19
19
  task 'publish' do
20
20
  system("gem push bootstrap-wysihtml5-rails-#{BootstrapWysihtml5Rails::Rails::VERSION}.gem")
21
+ system("git push")
21
22
  end
@@ -1,5 +1,5 @@
1
1
  module BootstrapWysihtml5Rails
2
2
  module Rails
3
- VERSION = "0.2.4"
3
+ VERSION = "0.2.5"
4
4
  end
5
5
  end
@@ -19,14 +19,54 @@
19
19
  //,+ "<a class='btn' data-wysihtml5-command='underline' title='CTRL+U'>Underline</a>"
20
20
  + "</div>"
21
21
  + "</li>",
22
- "lists": "<li>"
23
- + "<div class='btn-group'>"
24
- + "<a class='btn' data-wysihtml5-command='insertUnorderedList' title='Unordered List'><i class='icon-list'></i></a>"
25
- + "<a class='btn' data-wysihtml5-command='insertOrderedList' title='Ordered List'><i class='icon-th-list'></i></a>"
26
- + "<a class='btn' data-wysihtml5-command='Outdent' title='Outdent'><i class='icon-indent-right'></i></a>"
27
- + "<a class='btn' data-wysihtml5-command='Indent' title='Indent'><i class='icon-indent-left'></i></a>"
28
- + "</div>"
29
- + "</li>",
22
+ "lists": "<li>"
23
+ + "<div class='btn-group'>"
24
+ + "<a class='btn' data-wysihtml5-command='insertUnorderedList' title='Unordered List'><i class='icon-list'></i></a>"
25
+ + "<a class='btn' data-wysihtml5-command='insertOrderedList' title='Ordered List'><i class='icon-th-list'></i></a>"
26
+ + "<a class='btn' data-wysihtml5-command='Outdent' title='Outdent'><i class='icon-indent-right'></i></a>"
27
+ + "<a class='btn' data-wysihtml5-command='Indent' title='Indent'><i class='icon-indent-left'></i></a>"
28
+ + "</div>"
29
+ + "</li>",
30
+
31
+ "link": "<li>"
32
+
33
+ + "<div class='bootstrap-wysihtml5-insert-link-modal modal hide fade'>"
34
+ + "<div class='modal-header'>"
35
+ + "<a class='close' data-dismiss='modal'>×</a>"
36
+ + "<h3>Insert Link</h3>"
37
+ + "</div>"
38
+ + "<div class='modal-body'>"
39
+ + "<input value='http://' class='bootstrap-wysihtml5-insert-link-url input-xlarge'>"
40
+ + "</div>"
41
+ + "<div class='modal-footer'>"
42
+ + "<a href='#' class='btn' data-dismiss='modal'>Cancel</a>"
43
+ + "<a href='#' class='btn btn-primary' data-dismiss='modal'>Insert link</a>"
44
+ + "</div>"
45
+ + "</div>"
46
+
47
+ + "<a class='btn' data-wysihtml5-command='createLink' title='Link'><i class='icon-share'></i></a>"
48
+
49
+ + "</li>",
50
+
51
+ "image": "<li>"
52
+
53
+ + "<div class='bootstrap-wysihtml5-insert-image-modal modal hide fade'>"
54
+ + "<div class='modal-header'>"
55
+ + "<a class='close' data-dismiss='modal'>×</a>"
56
+ + "<h3>Insert Image</h3>"
57
+ + "</div>"
58
+ + "<div class='modal-body'>"
59
+ + "<input value='http://' class='bootstrap-wysihtml5-insert-link-url input-xlarge'>"
60
+ + "</div>"
61
+ + "<div class='modal-footer'>"
62
+ + "<a href='#' class='btn' data-dismiss='modal'>Cancel</a>"
63
+ + "<a href='#' class='btn btn-primary' data-dismiss='modal'>Insert image</a>"
64
+ + "</div>"
65
+ + "</div>"
66
+
67
+ + "<a class='btn' data-wysihtml5-command='insertImage' title='Insert image'><i class='icon-picture'></i></a>"
68
+
69
+ + "</li>",
30
70
 
31
71
  "html":
32
72
  "<li>"
@@ -41,6 +81,8 @@
41
81
  "emphasis": true,
42
82
  "lists": true,
43
83
  "html": false,
84
+ "link": true,
85
+ "image": false,
44
86
  events: {},
45
87
  parserRules: {
46
88
  tags: {
@@ -53,6 +95,14 @@
53
95
  "h1": {},
54
96
  "h2": {},
55
97
  "u": 1,
98
+ "img": {
99
+ "check_attributes": {
100
+ "width": "numbers",
101
+ "alt": "alt",
102
+ "src": "url",
103
+ "height": "numbers"
104
+ }
105
+ },
56
106
  "a": {
57
107
  set_attributes: {
58
108
  target: "_blank",
@@ -70,6 +120,8 @@
70
120
  this.el = el;
71
121
  this.toolbar = this.createToolbar(el, options || defaultOptions);
72
122
  this.editor = this.createEditor(options);
123
+
124
+ window.editor = this.editor;
73
125
 
74
126
  $('iframe.wysihtml5-sandbox').each(function(i, el){
75
127
  $(el.contentWindow).off('focus.wysihtml5').on({
@@ -78,6 +130,8 @@
78
130
  }
79
131
  });
80
132
  });
133
+
134
+
81
135
  };
82
136
 
83
137
  Wysihtml5.prototype = {
@@ -105,11 +159,12 @@
105
159
  },
106
160
 
107
161
  createToolbar: function(el, options) {
162
+ var self = this;
108
163
  var toolbar = $("<ul/>", {
109
- id : el.attr('id') + "-wysihtml5-toolbar",
110
- class : "wysihtml5-toolbar",
111
- style: "display:none"
112
- });
164
+ 'id' : el.attr('id') + "-wysihtml5-toolbar",
165
+ 'class' : "wysihtml5-toolbar",
166
+ 'style': "display:none"
167
+ });
113
168
 
114
169
  for(var key in defaultOptions) {
115
170
  var value = false;
@@ -126,10 +181,15 @@
126
181
  toolbar.append(templates[key]);
127
182
 
128
183
  if(key == "html") {
129
- var changeViewSelector = "a[data-wysihtml5-action='change_view']";
130
- toolbar.find(changeViewSelector).click(function(e) {
131
- toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled');
132
- });
184
+ this.initHtml(toolbar);
185
+ }
186
+
187
+ if(key == "link") {
188
+ this.initInsertLink(toolbar);
189
+ }
190
+
191
+ if(key == "image") {
192
+ this.initInsertImage(toolbar);
133
193
  }
134
194
  }
135
195
  }
@@ -144,6 +204,91 @@
144
204
  this.el.before(toolbar);
145
205
 
146
206
  return toolbar;
207
+ },
208
+
209
+ initHtml: function(toolbar) {
210
+ var changeViewSelector = "a[data-wysihtml5-action='change_view']";
211
+ toolbar.find(changeViewSelector).click(function(e) {
212
+ toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled');
213
+ });
214
+ },
215
+
216
+ initInsertImage: function(toolbar) {
217
+ var self = this;
218
+ var insertImageModal = toolbar.find('.bootstrap-wysihtml5-insert-image-modal');
219
+ var urlInput = insertImageModal.find('.bootstrap-wysihtml5-insert-image-url');
220
+ var insertButton = insertImageModal.find('a.btn-primary');
221
+
222
+ var insertImage = function() {
223
+ var url = urlInput.val();
224
+ urlInput.val('');
225
+ self.editor.composer.commands.exec("createLink", {
226
+ href: url,
227
+ target: "_blank",
228
+ rel: "nofollow"
229
+ });
230
+ };
231
+
232
+ urlInput.keypress(function(e) {
233
+ if(e.which == 13) {
234
+ insertImage();
235
+ insertImageModal.modal('hide');
236
+ }
237
+ });
238
+
239
+ insertButton.click(insertImage);
240
+
241
+ insertImageModal.on('shown', function() {
242
+ urlInput.focus();
243
+ });
244
+
245
+ insertImageModal.on('hide', function() {
246
+ self.editor.currentView.element.focus();
247
+ });
248
+
249
+ toolbar.find('a[data-wysihtml5-command=insertImage]').click(function() {
250
+ insertImageModal.modal('show');
251
+ });
252
+ },
253
+
254
+ initInsertLink: function(toolbar) {
255
+ var self = this;
256
+ var insertLinkModal = toolbar.find('.bootstrap-wysihtml5-insert-link-modal');
257
+ var urlInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-url');
258
+ var insertButton = insertLinkModal.find('a.btn-primary');
259
+ var initialValue = urlInput.val();
260
+
261
+ var insertLink = function() {
262
+ var url = urlInput.val();
263
+ urlInput.val(initialValue);
264
+ self.editor.composer.commands.exec("createLink", {
265
+ href: url,
266
+ target: "_blank",
267
+ rel: "nofollow"
268
+ });
269
+ };
270
+ var pressedEnter = false;
271
+
272
+ urlInput.keypress(function(e) {
273
+ if(e.which == 13) {
274
+ insertLink();
275
+ insertLinkModal.modal('hide');
276
+ }
277
+ });
278
+
279
+ insertButton.click(insertLink);
280
+
281
+ insertLinkModal.on('shown', function() {
282
+ urlInput.focus();
283
+ });
284
+
285
+ insertLinkModal.on('hide', function() {
286
+ self.editor.currentView.element.focus();
287
+ });
288
+
289
+ toolbar.find('a[data-wysihtml5-command=createLink]').click(function() {
290
+ insertLinkModal.modal('show');
291
+ });
147
292
  }
148
293
  };
149
294
 
@@ -156,4 +301,4 @@
156
301
 
157
302
  $.fn.wysihtml5.Constructor = Wysihtml5;
158
303
 
159
- }(window.jQuery, window.wysihtml5);
304
+ }(window.jQuery, window.wysihtml5);
@@ -1,15 +1,15 @@
1
1
  /**
2
- * @license wysihtml5 v0.3.0_rc1
2
+ * @license wysihtml5 v0.3.0_rc2
3
3
  * https://github.com/xing/wysihtml5
4
4
  *
5
5
  * Author: Christopher Blum (https://github.com/tiff)
6
6
  *
7
- * Copyright (C) 2011 XING AG
8
- * Licensed under GNU General Public License
7
+ * Copyright (C) 2012 XING AG
8
+ * Licensed under the MIT license (MIT)
9
9
  *
10
10
  */
11
11
  var wysihtml5 = {
12
- version: "0.3.0_rc1",
12
+ version: "0.3.0_rc2",
13
13
 
14
14
  // namespaces
15
15
  commands: {},
@@ -3383,8 +3383,6 @@ Base = Base.extend({
3383
3383
  }
3384
3384
  });/**
3385
3385
  * Detect browser support for specific features
3386
- *
3387
- * @author Christopher Blum <christopher.blum@xing.com>
3388
3386
  */
3389
3387
  wysihtml5.browser = (function() {
3390
3388
  var userAgent = navigator.userAgent,
@@ -3393,6 +3391,7 @@ wysihtml5.browser = (function() {
3393
3391
  isIE = userAgent.indexOf("MSIE") !== -1 && userAgent.indexOf("Opera") === -1,
3394
3392
  isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1,
3395
3393
  isWebKit = userAgent.indexOf("AppleWebKit/") !== -1,
3394
+ isChrome = userAgent.indexOf("Chrome/") !== -1,
3396
3395
  isOpera = userAgent.indexOf("Opera/") !== -1;
3397
3396
 
3398
3397
  function iosVersion(userAgent) {
@@ -3716,6 +3715,24 @@ wysihtml5.browser = (function() {
3716
3715
  */
3717
3716
  crashesWhenDefineProperty: function(property) {
3718
3717
  return isIE && (property === "XMLHttpRequest" || property === "XDomainRequest");
3718
+ },
3719
+
3720
+ /**
3721
+ * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
3722
+ */
3723
+ doesAsyncFocus: function() {
3724
+ return isIE;
3725
+ },
3726
+
3727
+ /**
3728
+ * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document
3729
+ */
3730
+ hasProblemsSettingCaretAfterImg: function() {
3731
+ return isIE;
3732
+ },
3733
+
3734
+ hasUndoInContextMenu: function() {
3735
+ return isGecko || isChrome || isOpera;
3719
3736
  }
3720
3737
  };
3721
3738
  })();wysihtml5.lang.array = function(arr) {
@@ -3905,7 +3922,7 @@ wysihtml5.browser = (function() {
3905
3922
  * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
3906
3923
  *
3907
3924
  * @param {Element} element Container element in which to search for urls
3908
- * @author Christopher Blum <christopher.blum@xing.com>
3925
+ *
3909
3926
  * @example
3910
3927
  * <div id="text-container">Please click here: www.google.com</div>
3911
3928
  * <script>wysihtml5.dom.autoLink(document.getElementById("text-container"));</script>
@@ -4089,7 +4106,6 @@ wysihtml5.dom.contains = (function() {
4089
4106
  })();/**
4090
4107
  * Converts an HTML fragment/element into a unordered/ordered list
4091
4108
  *
4092
- * @author Christopher Blum <christopher.blum@xing.com>
4093
4109
  * @param {Element} element The element which should be turned into a list
4094
4110
  * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
4095
4111
  * @return {Element} The created list
@@ -4170,8 +4186,6 @@ wysihtml5.dom.convertToList = (function() {
4170
4186
  })();/**
4171
4187
  * Copy a set of attributes from one element to another
4172
4188
  *
4173
- * @author Christopher Blum <christopher.blum@xing.com>
4174
- *
4175
4189
  * @param {Array} attributesToCopy List of attributes which should be copied
4176
4190
  * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
4177
4191
  * copy the attributes from., this again returns an object which provides a method named "to" which can be invoked
@@ -4210,8 +4224,6 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
4210
4224
  *
4211
4225
  * Interesting article on how to copy styles
4212
4226
  *
4213
- * @author Christopher Blum <christopher.blum@xing.com>
4214
- *
4215
4227
  * @param {Array} stylesToCopy List of styles which should be copied
4216
4228
  * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
4217
4229
  * copy the styles from., this again returns an object which provides a method named "to" which can be invoked
@@ -4306,7 +4318,6 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
4306
4318
  * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
4307
4319
  * when inserted via innerHTML
4308
4320
  *
4309
- * @author Christopher Blum <christopher.blum@xing.com>
4310
4321
  * @param {String} html The html which should be wrapped in a dom element
4311
4322
  * @param {Obejct} [context] Document object of the context the html belongs to
4312
4323
  *
@@ -4368,7 +4379,6 @@ wysihtml5.dom.getAsDom = (function() {
4368
4379
  * Walks the dom tree from the given node up until it finds a match
4369
4380
  * Designed for optimal performance.
4370
4381
  *
4371
- * @author Christopher Blum <christopher.blum@xing.com>
4372
4382
  * @param {Element} node The from which to check the parent nodes
4373
4383
  * @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp)
4374
4384
  * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
@@ -4446,7 +4456,7 @@ wysihtml5.dom.getParentElement = (function() {
4446
4456
  *
4447
4457
  * @param {Element} element The element on which to retrieve the style
4448
4458
  * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
4449
- * @author Christopher Blum <christopher.blum@xing.com>
4459
+ *
4450
4460
  * @example
4451
4461
  * wysihtml5.dom.getStyle("display").from(document.body);
4452
4462
  * // => "block"
@@ -4485,11 +4495,11 @@ wysihtml5.dom.getStyle = (function() {
4485
4495
  // gives you the original "50%".
4486
4496
  // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
4487
4497
  if (currentStyle) {
4488
- try {
4498
+ try {
4489
4499
  return currentStyle[camelizedProperty];
4490
- } catch(e) {
4491
- //ie fails for some reason. need to investigate
4492
- }
4500
+ } catch(e) {
4501
+ //ie will occasionally fail for unknown reasons. swallowing exception
4502
+ }
4493
4503
  }
4494
4504
 
4495
4505
  var win = doc.defaultView || doc.parentWindow,
@@ -4518,7 +4528,6 @@ wysihtml5.dom.getStyle = (function() {
4518
4528
  * Optimized for being heavily executed
4519
4529
  * Unleashes the power of live node lists
4520
4530
  *
4521
- * @author Christopher Blum <christopher.blum@xing.com>
4522
4531
  * @param {Object} doc The document object of the context where to check
4523
4532
  * @param {String} tagName Upper cased tag name
4524
4533
  * @example
@@ -4546,7 +4555,6 @@ wysihtml5.dom.hasElementWithTagName = (function() {
4546
4555
  * Optimized for being heavily executed
4547
4556
  * Unleashes the power of live node lists
4548
4557
  *
4549
- * @author Christopher Blum <christopher.blum@xing.com>
4550
4558
  * @param {Object} doc The document object of the context where to check
4551
4559
  * @param {String} tagName Upper cased tag name
4552
4560
  * @example
@@ -4614,8 +4622,6 @@ wysihtml5.dom.insert = function(elementToInsert) {
4614
4622
  };/**
4615
4623
  * Method to set dom events
4616
4624
  *
4617
- * @author Christopher Blum <christopher.blum@xing.com>
4618
- *
4619
4625
  * @example
4620
4626
  * wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
4621
4627
  */
@@ -4668,8 +4674,6 @@ wysihtml5.dom.observe = function(element, eventNames, handler) {
4668
4674
  * HTML Sanitizer
4669
4675
  * Rewrites the HTML based on given rules
4670
4676
  *
4671
- * @author Christopher Blum <christopher.blum@xing.com>
4672
- *
4673
4677
  * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
4674
4678
  * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
4675
4679
  * be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
@@ -5115,7 +5119,6 @@ wysihtml5.dom.parse = (function() {
5115
5119
  })();/**
5116
5120
  * Checks for empty text node childs and removes them
5117
5121
  *
5118
- * @author Christopher Blum <christopher.blum@xing.com>
5119
5122
  * @param {Element} node The element in which to cleanup
5120
5123
  * @example
5121
5124
  * wysihtml5.dom.removeEmptyTextNodes(element);
@@ -5135,7 +5138,6 @@ wysihtml5.dom.removeEmptyTextNodes = function(node) {
5135
5138
  /**
5136
5139
  * Renames an element (eg. a <div> to a <p>) and keeps its childs
5137
5140
  *
5138
- * @author Christopher Blum <christopher.blum@xing.com>
5139
5141
  * @param {Element} element The list element which should be renamed
5140
5142
  * @param {Element} newNodeName The desired tag name
5141
5143
  *
@@ -5170,7 +5172,6 @@ wysihtml5.dom.renameElement = function(element, newNodeName) {
5170
5172
  };/**
5171
5173
  * Takes an element, removes it and replaces it with it's childs
5172
5174
  *
5173
- * @author Christopher Blum <christopher.blum@xing.com>
5174
5175
  * @param {Object} node The node which to replace with it's child nodes
5175
5176
  * @example
5176
5177
  * <div id="foo">
@@ -5201,7 +5202,6 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5201
5202
  /**
5202
5203
  * Unwraps an unordered/ordered list
5203
5204
  *
5204
- * @author Christopher Blum <christopher.blum@xing.com>
5205
5205
  * @param {Element} element The list element which should be unwrapped
5206
5206
  *
5207
5207
  * @example
@@ -5285,10 +5285,8 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5285
5285
  * - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
5286
5286
  * can do anything as if the sandbox attribute wasn't set
5287
5287
  *
5288
- * @author Christopher Blum <christopher.blum@xing.com>
5289
- *
5290
5288
  * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
5291
- * @param {Object} [config] Optional parameters, see defaultConfig property for more info
5289
+ * @param {Object} [config] Optional parameters
5292
5290
  *
5293
5291
  * @example
5294
5292
  * new wysihtml5.dom.Sandbox(function(sandbox) {
@@ -5299,9 +5297,6 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5299
5297
  var /**
5300
5298
  * Default configuration
5301
5299
  */
5302
- defaultConfig = {
5303
- uaCompatible: "IE=Edge" // X-UA-Compatible meta tag value (Document compatibility mode)
5304
- },
5305
5300
  doc = document,
5306
5301
  /**
5307
5302
  * Properties to unset/protect on the window object
@@ -5332,7 +5327,7 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5332
5327
 
5333
5328
  constructor: function(readyCallback, config) {
5334
5329
  this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
5335
- this.config = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
5330
+ this.config = wysihtml5.lang.object({}).merge(config).get();
5336
5331
  this.iframe = this._createIframe();
5337
5332
  },
5338
5333
 
@@ -5429,12 +5424,11 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5429
5424
 
5430
5425
  var that = this,
5431
5426
  iframeWindow = iframe.contentWindow,
5432
- iframeDocument = iframe.contentDocument || iframe.contentWindow.document,
5427
+ iframeDocument = iframe.contentWindow.document,
5433
5428
  charset = doc.characterSet || doc.charset || "utf-8",
5434
5429
  sandboxHtml = this._getHtml({
5435
5430
  charset: charset,
5436
- stylesheets: this.config.stylesheets,
5437
- uaCompatible: this.config.uaCompatible
5431
+ stylesheets: this.config.stylesheets
5438
5432
  });
5439
5433
 
5440
5434
  // Create the basic dom tree including proper DOCTYPE and charset
@@ -5442,8 +5436,8 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5442
5436
  iframeDocument.write(sandboxHtml);
5443
5437
  iframeDocument.close();
5444
5438
 
5445
- this.getWindow = function() { return iframeWindow; };
5446
- this.getDocument = function() { return iframeDocument; };
5439
+ this.getWindow = function() { return iframe.contentWindow; };
5440
+ this.getDocument = function() { return iframe.contentWindow.document; };
5447
5441
 
5448
5442
  // Catch js errors and pass them to the parent's onerror event
5449
5443
  // addEventListener("error") doesn't work properly in some browsers
@@ -5496,7 +5490,7 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5496
5490
 
5497
5491
  return wysihtml5.lang.string(
5498
5492
  '<!DOCTYPE html><html><head>'
5499
- + '<meta http-equiv="X-UA-Compatible" content="#{uaCompatible}"><meta charset="#{charset}">#{stylesheets}</head>'
5493
+ + '<meta charset="#{charset}">#{stylesheets}</head>'
5500
5494
  + '<body></body></html>'
5501
5495
  ).interpolate(templateVars);
5502
5496
  },
@@ -5567,8 +5561,6 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
5567
5561
  * - div[contentEditable] elements don't support it
5568
5562
  * - older browsers (such as IE8 and Firefox 3.6) don't support it at all
5569
5563
  *
5570
- * @author Christopher Blum <christopher.blum@xing.com>
5571
- *
5572
5564
  * @param {Object} parent Instance of main wysihtml5.Editor class
5573
5565
  * @param {Element} view Instance of wysihtml5.views.* class
5574
5566
  * @param {String} placeholderText
@@ -5679,7 +5671,6 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5679
5671
  })();/**
5680
5672
  * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
5681
5673
  *
5682
- * @author Christopher Blum <christopher.blum@xing.com>
5683
5674
  * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
5684
5675
  * @exaple
5685
5676
  * wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
@@ -5699,8 +5690,8 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5699
5690
  }, 0);
5700
5691
  };
5701
5692
 
5702
- return function(contentEditableElement) {
5703
- dom.observe(contentEditableElement, ["cut", "keydown"], clearIfNecessary);
5693
+ return function(composer) {
5694
+ dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
5704
5695
  };
5705
5696
  })();
5706
5697
 
@@ -5709,7 +5700,6 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5709
5700
  /**
5710
5701
  * In Opera when the caret is in the first and only item of a list (<ul><li>|</li></ul>) and the list is the first child of the contentEditable element, it's impossible to delete the list by hitting backspace
5711
5702
  *
5712
- * @author Christopher Blum <christopher.blum@xing.com>
5713
5703
  * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
5714
5704
  * @exaple
5715
5705
  * wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
@@ -5745,14 +5735,14 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5745
5735
  list.parentNode.removeChild(list);
5746
5736
  };
5747
5737
 
5748
- return function(contentEditableElement) {
5749
- dom.observe(contentEditableElement, "keydown", function(event) {
5738
+ return function(composer) {
5739
+ dom.observe(composer.element, "keydown", function(event) {
5750
5740
  if (event.keyCode !== wysihtml5.BACKSPACE_KEY) {
5751
5741
  return;
5752
5742
  }
5753
5743
 
5754
- var element = wysihtml5.selection.getSelectedNode();
5755
- clearIfNecessary(element, contentEditableElement);
5744
+ var element = composer.selection.getSelectedNode();
5745
+ clearIfNecessary(element, composer.element);
5756
5746
  });
5757
5747
  };
5758
5748
  })();
@@ -5792,8 +5782,6 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5792
5782
  * - Opera & IE insert new <p> on return
5793
5783
  * - Chrome & Safari insert new <div> on return
5794
5784
  * - Firefox inserts <br> on return (yippie!)
5795
-
5796
- * @author Christopher Blum <christopher.blum@xing.com>
5797
5785
  *
5798
5786
  * @param {Element} element
5799
5787
  *
@@ -5805,72 +5793,71 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5805
5793
  USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
5806
5794
  LIST_TAGS = ["UL", "OL", "MENU"];
5807
5795
 
5808
- function unwrap(selectedNode) {
5809
- var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
5810
- if (!parentElement) {
5811
- return;
5812
- }
5813
-
5814
- var invisibleSpace = document.createTextNode(wysihtml5.INVISIBLE_SPACE);
5815
- dom.insert(invisibleSpace).before(parentElement);
5816
- dom.replaceWithChildNodes(parentElement);
5817
- wysihtml5.selection.selectNode(invisibleSpace);
5818
- }
5819
-
5820
- function keyDown(event) {
5821
- var keyCode = event.keyCode;
5822
- if (event.shiftKey || (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY)) {
5823
- return;
5796
+ wysihtml5.quirks.insertLineBreakOnReturn = function(composer) {
5797
+ function unwrap(selectedNode) {
5798
+ var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
5799
+ if (!parentElement) {
5800
+ return;
5801
+ }
5802
+
5803
+ var invisibleSpace = document.createTextNode(wysihtml5.INVISIBLE_SPACE);
5804
+ dom.insert(invisibleSpace).before(parentElement);
5805
+ dom.replaceWithChildNodes(parentElement);
5806
+ composer.selection.selectNode(invisibleSpace);
5824
5807
  }
5825
-
5826
- var element = event.target,
5827
- selectedNode = wysihtml5.selection.getSelectedNode(),
5828
- blockElement = dom.getParentElement(selectedNode, { nodeName: USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS }, 4);
5829
- if (blockElement) {
5830
- // Some browsers create <p> elements after leaving a list
5831
- // check after keydown of backspace and return whether a <p> got inserted and unwrap it
5832
- if (blockElement.nodeName === "LI" && (keyCode === wysihtml5.ENTER_KEY || keyCode === wysihtml5.BACKSPACE_KEY)) {
5833
- setTimeout(function() {
5834
- var selectedNode = wysihtml5.selection.getSelectedNode(),
5835
- list,
5836
- div;
5837
- if (!selectedNode) {
5838
- return;
5839
- }
5840
-
5841
- list = dom.getParentElement(selectedNode, {
5842
- nodeName: LIST_TAGS
5843
- }, 2);
5844
-
5845
- if (list) {
5846
- return;
5847
- }
5848
-
5849
- unwrap(selectedNode);
5850
- }, 0);
5851
- } else if (blockElement.nodeName.match(/H[1-6]/) && keyCode === wysihtml5.ENTER_KEY) {
5852
- setTimeout(function() {
5853
- unwrap(wysihtml5.selection.getSelectedNode());
5854
- }, 0);
5855
- }
5856
- return;
5808
+
5809
+ function keyDown(event) {
5810
+ var keyCode = event.keyCode;
5811
+ if (event.shiftKey || (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY)) {
5812
+ return;
5813
+ }
5814
+
5815
+ var element = event.target,
5816
+ selectedNode = composer.selection.getSelectedNode(),
5817
+ blockElement = dom.getParentElement(selectedNode, { nodeName: USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS }, 4);
5818
+ if (blockElement) {
5819
+ // Some browsers create <p> elements after leaving a list
5820
+ // check after keydown of backspace and return whether a <p> got inserted and unwrap it
5821
+ if (blockElement.nodeName === "LI" && (keyCode === wysihtml5.ENTER_KEY || keyCode === wysihtml5.BACKSPACE_KEY)) {
5822
+ setTimeout(function() {
5823
+ var selectedNode = composer.selection.getSelectedNode(),
5824
+ list,
5825
+ div;
5826
+ if (!selectedNode) {
5827
+ return;
5828
+ }
5829
+
5830
+ list = dom.getParentElement(selectedNode, {
5831
+ nodeName: LIST_TAGS
5832
+ }, 2);
5833
+
5834
+ if (list) {
5835
+ return;
5836
+ }
5837
+
5838
+ unwrap(selectedNode);
5839
+ }, 0);
5840
+ } else if (blockElement.nodeName.match(/H[1-6]/) && keyCode === wysihtml5.ENTER_KEY) {
5841
+ setTimeout(function() {
5842
+ unwrap(composer.selection.getSelectedNode());
5843
+ }, 0);
5844
+ }
5845
+ return;
5846
+ }
5847
+
5848
+ if (keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
5849
+ composer.commands.exec("insertLineBreak");
5850
+ event.preventDefault();
5851
+ }
5857
5852
  }
5858
5853
 
5859
- if (keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
5860
- wysihtml5.commands.exec("insertLineBreak");
5861
- event.preventDefault();
5862
- }
5863
- }
5864
-
5865
- wysihtml5.quirks.insertLineBreakOnReturn = function(element) {
5866
5854
  // keypress doesn't fire when you hit backspace
5867
- dom.observe(element.ownerDocument, "keydown", keyDown);
5855
+ dom.observe(composer.element.ownerDocument, "keydown", keyDown);
5868
5856
  };
5869
5857
  })(wysihtml5);/**
5870
5858
  * Force rerendering of a given element
5871
5859
  * Needed to fix display misbehaviors of IE
5872
5860
  *
5873
- * @author Christopher Blum <christopher.blum@xing.com>
5874
5861
  * @param {Element} element The element object which needs to be rerendered
5875
5862
  * @example
5876
5863
  * wysihtml5.quirks.redraw(document.body);
@@ -5889,7 +5876,13 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5889
5876
  doc.execCommand("italic", false, null);
5890
5877
  } catch(e) {}
5891
5878
  };
5892
- })(wysihtml5);(function(wysihtml5) {
5879
+ })(wysihtml5);/**
5880
+ * Selection API
5881
+ *
5882
+ * @example
5883
+ * var selection = new wysihtml5.Selection(editor);
5884
+ */
5885
+ (function(wysihtml5) {
5893
5886
  var dom = wysihtml5.dom;
5894
5887
 
5895
5888
  function _getCumulativeOffsetTop(element) {
@@ -5903,18 +5896,15 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5903
5896
  return top;
5904
5897
  }
5905
5898
 
5906
- wysihtml5.selection = {
5907
-
5908
- /**
5909
- * Setup selection for editor
5910
- *
5911
- * @param {Object} doc Document object of the context
5912
- */
5913
- initialize: function(doc) {
5899
+ wysihtml5.Selection = Base.extend(
5900
+ /** @scope wysihtml5.Selection.prototype */ {
5901
+ constructor: function(editor) {
5914
5902
  // Make sure that our external range library is initialized
5915
5903
  window.rangy.init();
5916
5904
 
5917
- this.doc = doc;
5905
+ this.editor = editor;
5906
+ this.composer = editor.composer;
5907
+ this.doc = this.composer.doc;
5918
5908
  },
5919
5909
 
5920
5910
  /**
@@ -5928,7 +5918,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5928
5918
  },
5929
5919
 
5930
5920
  /**
5931
- * Restore a selection retrieved via wysihtml5.selection.getBookmark
5921
+ * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark
5932
5922
  *
5933
5923
  * @param {Object} bookmark An object that represents the current selection
5934
5924
  */
@@ -5945,7 +5935,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5945
5935
  *
5946
5936
  * @param {Object} node The element or text node where to position the caret in front of
5947
5937
  * @example
5948
- * wysihtml5.selection.setBefore(myElement);
5938
+ * selection.setBefore(myElement);
5949
5939
  */
5950
5940
  setBefore: function(node) {
5951
5941
  var range = rangy.createRange(this.doc);
@@ -5959,7 +5949,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5959
5949
  *
5960
5950
  * @param {Object} node The element or text node where to position the caret in front of
5961
5951
  * @example
5962
- * wysihtml5.selection.setBefore(myElement);
5952
+ * selection.setBefore(myElement);
5963
5953
  */
5964
5954
  setAfter: function(node) {
5965
5955
  var range = rangy.createRange(this.doc);
@@ -5973,7 +5963,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
5973
5963
  *
5974
5964
  * @param {Element} node The node/element to select
5975
5965
  * @example
5976
- * wysihtml5.selection.selectNode(document.getElementById("my-image"));
5966
+ * selection.selectNode(document.getElementById("my-image"));
5977
5967
  */
5978
5968
  selectNode: function(node) {
5979
5969
  var range = rangy.createRange(this.doc),
@@ -6011,7 +6001,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
6011
6001
  * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
6012
6002
  * @return {Object} The node that contains the caret
6013
6003
  * @example
6014
- * var nodeThatContainsCaret = wysihtml5.selection.getSelectedNode();
6004
+ * var nodeThatContainsCaret = selection.getSelectedNode();
6015
6005
  */
6016
6006
  getSelectedNode: function(controlRange) {
6017
6007
  var selection,
@@ -6128,7 +6118,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
6128
6118
  *
6129
6119
  * @param {String} html HTML string to insert
6130
6120
  * @example
6131
- * wysihtml5.selection.insertHTML("<p>foobar</p>");
6121
+ * selection.insertHTML("<p>foobar</p>");
6132
6122
  */
6133
6123
  insertHTML: function(html) {
6134
6124
  var range = rangy.createRange(this.doc),
@@ -6145,7 +6135,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
6145
6135
  *
6146
6136
  * @param {Object} node HTML string to insert
6147
6137
  * @example
6148
- * wysihtml5.selection.insertNode(document.createTextNode("foobar"));
6138
+ * selection.insertNode(document.createTextNode("foobar"));
6149
6139
  */
6150
6140
  insertNode: function(node) {
6151
6141
  var range = this.getRange();
@@ -6180,11 +6170,10 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
6180
6170
  * Scroll the current caret position into the view
6181
6171
  * FIXME: This is a bit hacky, there might be a smarter way of doing this
6182
6172
  *
6183
- * @param {Object} element A scrollable element in which the caret is currently positioned
6184
6173
  * @example
6185
- * wysihtml5.selection.scrollIntoView(element);
6174
+ * selection.scrollIntoView();
6186
6175
  */
6187
- scrollIntoView: function(element) {
6176
+ scrollIntoView: function() {
6188
6177
  var doc = this.doc,
6189
6178
  hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
6190
6179
  tempElement = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() {
@@ -6199,8 +6188,8 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
6199
6188
  this.insertNode(tempElement);
6200
6189
  offsetTop = _getCumulativeOffsetTop(tempElement);
6201
6190
  tempElement.parentNode.removeChild(tempElement);
6202
- if (offsetTop > element.scrollTop) {
6203
- element.scrollTop = offsetTop;
6191
+ if (offsetTop > doc.body.scrollTop) {
6192
+ doc.body.scrollTop = offsetTop;
6204
6193
  }
6205
6194
  }
6206
6195
  },
@@ -6302,18 +6291,16 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
6302
6291
  selection = rangy.getSelection(win);
6303
6292
  return selection.setSingleRange(range);
6304
6293
  }
6305
- };
6294
+ });
6306
6295
 
6307
6296
  })(wysihtml5);
6308
-
6309
6297
  /**
6310
- * @author Christopher Blum <christopher.blum@xing.com> to match WYSIHTML5 logic
6311
- * in order to be able ...
6298
+ * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
6299
+ * http://code.google.com/p/rangy/
6300
+ *
6301
+ * changed in order to be able ...
6312
6302
  * - to use custom tags
6313
6303
  * - to detect and replace similar css classes via reg exp
6314
- *
6315
- * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
6316
- * http://code.google.com/p/rangy/
6317
6304
  */
6318
6305
  (function(wysihtml5, rangy) {
6319
6306
  var defaultTagName = "span";
@@ -6742,22 +6729,23 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
6742
6729
  })(wysihtml5, rangy);/**
6743
6730
  * Rich Text Query/Formatting Commands
6744
6731
  *
6745
- * @author Christopher Blum <christopher.blum@xing.com>
6732
+ * @example
6733
+ * var commands = new wysihtml5.Commands(editor);
6746
6734
  */
6747
- wysihtml5.commands = {
6748
- initialize: function(editor) {
6735
+ wysihtml5.Commands = Base.extend(
6736
+ /** @scope wysihtml5.Commands.prototype */ {
6737
+ constructor: function(editor) {
6749
6738
  this.editor = editor;
6750
- this.element = editor.composer.element;
6751
- this.doc = this.element.ownerDocument;
6739
+ this.composer = editor.composer;
6740
+ this.doc = this.composer.doc;
6752
6741
  },
6753
6742
 
6754
6743
  /**
6755
6744
  * Check whether the browser supports the given command
6756
6745
  *
6757
- * @param {Object} element The element which has contentEditable=true
6758
6746
  * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
6759
6747
  * @example
6760
- * wysihtml5.commands.supports("createLink");
6748
+ * commands.supports("createLink");
6761
6749
  */
6762
6750
  support: function(command) {
6763
6751
  return wysihtml5.browser.supportsCommand(this.doc, command);
@@ -6766,20 +6754,19 @@ wysihtml5.commands = {
6766
6754
  /**
6767
6755
  * Check whether the browser supports the given command
6768
6756
  *
6769
- * @param {Object} element The element which has contentEditable=true
6770
6757
  * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
6771
6758
  * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
6772
6759
  * @example
6773
- * wysihtml5.commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
6760
+ * commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
6774
6761
  */
6775
6762
  exec: function(command, value) {
6776
- var obj = this[command],
6763
+ var obj = wysihtml5.commands[command],
6777
6764
  method = obj && obj.exec;
6778
6765
 
6779
6766
  this.editor.fire("beforecommand:composer");
6780
6767
 
6781
6768
  if (method) {
6782
- return method.call(obj, this.element, command, value);
6769
+ return method.call(obj, this.composer, command, value);
6783
6770
  } else {
6784
6771
  try {
6785
6772
  // try/catch for buggy firefox
@@ -6794,18 +6781,17 @@ wysihtml5.commands = {
6794
6781
  * Check whether the current command is active
6795
6782
  * If the caret is within a bold text, then calling this with command "bold" should return true
6796
6783
  *
6797
- * @param {Object} element The element which has contentEditable=true
6798
6784
  * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
6799
6785
  * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
6800
6786
  * @return {Boolean} Whether the command is active
6801
6787
  * @example
6802
- * var isCurrentSelectionBold = wysihtml5.commands.state("bold");
6788
+ * var isCurrentSelectionBold = commands.state("bold");
6803
6789
  */
6804
6790
  state: function(command, commandValue) {
6805
- var obj = this[command],
6791
+ var obj = wysihtml5.commands[command],
6806
6792
  method = obj && obj.state;
6807
6793
  if (method) {
6808
- return method.call(obj, this.element, command, commandValue);
6794
+ return method.call(obj, this.composer, command, commandValue);
6809
6795
  } else {
6810
6796
  try {
6811
6797
  // try/catch for buggy firefox
@@ -6819,17 +6805,16 @@ wysihtml5.commands = {
6819
6805
  /**
6820
6806
  * Get the current command's value
6821
6807
  *
6822
- * @param {Object} element The element which has contentEditable=true
6823
6808
  * @param {String} command The command string which to check (eg. "formatBlock")
6824
6809
  * @return {String} The command value
6825
6810
  * @example
6826
- * var currentBlockElement = wysihtml5.commands.value("formatBlock");
6811
+ * var currentBlockElement = commands.value("formatBlock");
6827
6812
  */
6828
6813
  value: function(command) {
6829
- var obj = this[command],
6814
+ var obj = wysihtml5.commands[command],
6830
6815
  method = obj && obj.value;
6831
6816
  if (method) {
6832
- method(this.element, command);
6817
+ return method.call(obj, this.composer, command);
6833
6818
  } else {
6834
6819
  try {
6835
6820
  // try/catch for buggy firefox
@@ -6839,21 +6824,21 @@ wysihtml5.commands = {
6839
6824
  }
6840
6825
  }
6841
6826
  }
6842
- };(function(wysihtml5) {
6827
+ });(function(wysihtml5) {
6843
6828
  var undef;
6844
6829
 
6845
6830
  wysihtml5.commands.bold = {
6846
- exec: function(element, command) {
6847
- return wysihtml5.commands.formatInline.exec(element, command, "b");
6831
+ exec: function(composer, command) {
6832
+ return wysihtml5.commands.formatInline.exec(composer, command, "b");
6848
6833
  },
6849
6834
 
6850
- state: function(element, command, color) {
6835
+ state: function(composer, command, color) {
6851
6836
  // element.ownerDocument.queryCommandState("bold") results:
6852
6837
  // firefox: only <b>
6853
6838
  // chrome: <b>, <strong>, <h1>, <h2>, ...
6854
6839
  // ie: <b>, <strong>
6855
6840
  // opera: <b>, <strong>
6856
- return wysihtml5.commands.formatInline.state(element, command, "b");
6841
+ return wysihtml5.commands.formatInline.state(composer, command, "b");
6857
6842
  },
6858
6843
 
6859
6844
  value: function() {
@@ -6867,9 +6852,9 @@ wysihtml5.commands = {
6867
6852
  NODE_NAME = "A",
6868
6853
  dom = wysihtml5.dom;
6869
6854
 
6870
- function _removeFormat(element, anchors) {
6871
- var length = anchors.length,
6872
- i = 0,
6855
+ function _removeFormat(composer, anchors) {
6856
+ var length = anchors.length,
6857
+ i = 0,
6873
6858
  anchor,
6874
6859
  codeElement,
6875
6860
  textContent;
@@ -6889,8 +6874,8 @@ wysihtml5.commands = {
6889
6874
  }
6890
6875
  }
6891
6876
 
6892
- function _format(element, attributes) {
6893
- var doc = element.ownerDocument,
6877
+ function _format(composer, attributes) {
6878
+ var doc = composer.doc,
6894
6879
  tempClass = "_wysihtml5-temp-" + (+new Date()),
6895
6880
  tempClassRegExp = /non-matching-class/g,
6896
6881
  i = 0,
@@ -6903,7 +6888,7 @@ wysihtml5.commands = {
6903
6888
  textContent,
6904
6889
  whiteSpace,
6905
6890
  j;
6906
- wysihtml5.commands.formatInline.exec(element, undef, NODE_NAME, tempClass, tempClassRegExp);
6891
+ wysihtml5.commands.formatInline.exec(composer, undef, NODE_NAME, tempClass, tempClassRegExp);
6907
6892
  anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass);
6908
6893
  length = anchors.length;
6909
6894
  for (; i<length; i++) {
@@ -6922,12 +6907,12 @@ wysihtml5.commands = {
6922
6907
  if (!hasElementChild && isEmpty) {
6923
6908
  dom.setTextContent(anchor, anchor.href);
6924
6909
  whiteSpace = doc.createTextNode(" ");
6925
- wysihtml5.selection.setAfter(anchor);
6926
- wysihtml5.selection.insertNode(whiteSpace);
6910
+ composer.selection.setAfter(anchor);
6911
+ composer.selection.insertNode(whiteSpace);
6927
6912
  elementToSetCaretAfter = whiteSpace;
6928
6913
  }
6929
6914
  }
6930
- wysihtml5.selection.setAfter(elementToSetCaretAfter);
6915
+ composer.selection.setAfter(elementToSetCaretAfter);
6931
6916
  }
6932
6917
 
6933
6918
  wysihtml5.commands.createLink = {
@@ -6940,28 +6925,26 @@ wysihtml5.commands = {
6940
6925
  *
6941
6926
  * @example
6942
6927
  * // either ...
6943
- * wysihtml5.commands.createLink.exec(element, "createLink", "http://www.google.de");
6928
+ * wysihtml5.commands.createLink.exec(composer, "createLink", "http://www.google.de");
6944
6929
  * // ... or ...
6945
- * wysihtml5.commands.createLink.exec(element, "createLink", { href: "http://www.google.de", target: "_blank" });
6930
+ * wysihtml5.commands.createLink.exec(composer, "createLink", { href: "http://www.google.de", target: "_blank" });
6946
6931
  */
6947
- exec: function(element, command, value) {
6948
- var doc = element.ownerDocument,
6949
- anchors = this.state(element, command);
6950
-
6932
+ exec: function(composer, command, value) {
6933
+ var anchors = this.state(composer, command);
6951
6934
  if (anchors) {
6952
6935
  // Selection contains links
6953
- wysihtml5.selection.executeAndRestore(function() {
6954
- _removeFormat(element, anchors);
6936
+ composer.selection.executeAndRestore(function() {
6937
+ _removeFormat(composer, anchors);
6955
6938
  });
6956
6939
  } else {
6957
6940
  // Create links
6958
6941
  value = typeof(value) === "object" ? value : { href: value };
6959
- _format(element, value);
6942
+ _format(composer, value);
6960
6943
  }
6961
6944
  },
6962
6945
 
6963
- state: function(element, command) {
6964
- return wysihtml5.commands.formatInline.state(element, command, "A");
6946
+ state: function(composer, command) {
6947
+ return wysihtml5.commands.formatInline.state(composer, command, "A");
6965
6948
  },
6966
6949
 
6967
6950
  value: function() {
@@ -6978,12 +6961,12 @@ wysihtml5.commands = {
6978
6961
  REG_EXP = /wysiwyg-font-size-[a-z]+/g;
6979
6962
 
6980
6963
  wysihtml5.commands.fontSize = {
6981
- exec: function(element, command, size) {
6982
- return wysihtml5.commands.formatInline.exec(element, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
6964
+ exec: function(composer, command, size) {
6965
+ return wysihtml5.commands.formatInline.exec(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
6983
6966
  },
6984
6967
 
6985
- state: function(element, command, size) {
6986
- return wysihtml5.commands.formatInline.state(element, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
6968
+ state: function(composer, command, size) {
6969
+ return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
6987
6970
  },
6988
6971
 
6989
6972
  value: function() {
@@ -7001,12 +6984,12 @@ wysihtml5.commands = {
7001
6984
  REG_EXP = /wysiwyg-color-[a-z]+/g;
7002
6985
 
7003
6986
  wysihtml5.commands.foreColor = {
7004
- exec: function(element, command, color) {
7005
- return wysihtml5.commands.formatInline.exec(element, command, "span", "wysiwyg-color-" + color, REG_EXP);
6987
+ exec: function(composer, command, color) {
6988
+ return wysihtml5.commands.formatInline.exec(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
7006
6989
  },
7007
6990
 
7008
- state: function(element, command, color) {
7009
- return wysihtml5.commands.formatInline.state(element, command, "span", "wysiwyg-color-" + color, REG_EXP);
6991
+ state: function(composer, command, color) {
6992
+ return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
7010
6993
  },
7011
6994
 
7012
6995
  value: function() {
@@ -7016,7 +6999,6 @@ wysihtml5.commands = {
7016
6999
  })(wysihtml5);(function(wysihtml5) {
7017
7000
  var undef,
7018
7001
  dom = wysihtml5.dom,
7019
- selection = wysihtml5.selection,
7020
7002
  DEFAULT_NODE_NAME = "DIV",
7021
7003
  // Following elements are grouped
7022
7004
  // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
@@ -7140,7 +7122,7 @@ wysihtml5.commands = {
7140
7122
  if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
7141
7123
  return;
7142
7124
  }
7143
- displayStyle = dom.getStyle("display").from(target);
7125
+ displayStyle = dom.getStyle("display").from(target);
7144
7126
  if (displayStyle.substr(0, 6) !== "inline") {
7145
7127
  // Make sure that only block elements receive the given class
7146
7128
  target.className += " " + className;
@@ -7153,12 +7135,12 @@ wysihtml5.commands = {
7153
7135
  }
7154
7136
  }
7155
7137
 
7156
- function _selectLineAndWrap(element) {
7157
- selection.selectLine();
7158
- selection.surround(element);
7138
+ function _selectLineAndWrap(composer, element) {
7139
+ composer.selection.selectLine();
7140
+ composer.selection.surround(element);
7159
7141
  _removeLineBreakBeforeAndAfter(element);
7160
7142
  _removeLastChildIfLineBreak(element);
7161
- selection.selectNode(element);
7143
+ composer.selection.selectNode(element);
7162
7144
  }
7163
7145
 
7164
7146
  function _hasClasses(element) {
@@ -7166,15 +7148,15 @@ wysihtml5.commands = {
7166
7148
  }
7167
7149
 
7168
7150
  wysihtml5.commands.formatBlock = {
7169
- exec: function(element, command, nodeName, className, classRegExp) {
7170
- var doc = element.ownerDocument,
7171
- blockElement = this.state(element, command, nodeName, className, classRegExp),
7151
+ exec: function(composer, command, nodeName, className, classRegExp) {
7152
+ var doc = composer.doc,
7153
+ blockElement = this.state(composer, command, nodeName, className, classRegExp),
7172
7154
  selectedNode;
7173
7155
 
7174
7156
  nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
7175
7157
 
7176
7158
  if (blockElement) {
7177
- selection.executeAndRestoreSimple(function() {
7159
+ composer.selection.executeAndRestoreSimple(function() {
7178
7160
  if (classRegExp) {
7179
7161
  _removeClass(blockElement, classRegExp);
7180
7162
  }
@@ -7194,13 +7176,13 @@ wysihtml5.commands = {
7194
7176
 
7195
7177
  // Find similiar block element and rename it (<h2 class="foo"></h2> => <h1 class="foo"></h1>)
7196
7178
  if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
7197
- selectedNode = selection.getSelectedNode();
7179
+ selectedNode = composer.selection.getSelectedNode();
7198
7180
  blockElement = dom.getParentElement(selectedNode, {
7199
- nodeName: BLOCK_ELEMENTS_GROUP
7181
+ nodeName: BLOCK_ELEMENTS_GROUP
7200
7182
  });
7201
7183
 
7202
7184
  if (blockElement) {
7203
- selection.executeAndRestoreSimple(function() {
7185
+ composer.selection.executeAndRestoreSimple(function() {
7204
7186
  // Rename current block element to new block element and add class
7205
7187
  if (nodeName) {
7206
7188
  blockElement = dom.renameElement(blockElement, nodeName);
@@ -7213,7 +7195,7 @@ wysihtml5.commands = {
7213
7195
  }
7214
7196
  }
7215
7197
 
7216
- if (wysihtml5.commands.support(command)) {
7198
+ if (composer.commands.support(command)) {
7217
7199
  _execCommand(doc, command, nodeName || DEFAULT_NODE_NAME, className);
7218
7200
  return;
7219
7201
  }
@@ -7222,12 +7204,12 @@ wysihtml5.commands = {
7222
7204
  if (className) {
7223
7205
  blockElement.className = className;
7224
7206
  }
7225
- _selectLineAndWrap(blockElement);
7207
+ _selectLineAndWrap(composer, blockElement);
7226
7208
  },
7227
7209
 
7228
- state: function(element, command, nodeName, className, classRegExp) {
7210
+ state: function(composer, command, nodeName, className, classRegExp) {
7229
7211
  nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
7230
- var selectedNode = selection.getSelectedNode();
7212
+ var selectedNode = composer.selection.getSelectedNode();
7231
7213
  return dom.getParentElement(selectedNode, {
7232
7214
  nodeName: nodeName,
7233
7215
  className: className,
@@ -7297,17 +7279,17 @@ wysihtml5.commands = {
7297
7279
  }
7298
7280
 
7299
7281
  wysihtml5.commands.formatInline = {
7300
- exec: function(element, command, tagName, className, classRegExp) {
7301
- var range = wysihtml5.selection.getRange();
7282
+ exec: function(composer, command, tagName, className, classRegExp) {
7283
+ var range = composer.selection.getRange();
7302
7284
  if (!range) {
7303
7285
  return false;
7304
7286
  }
7305
7287
  _getApplier(tagName, className, classRegExp).toggleRange(range);
7306
- wysihtml5.selection.setSelection(range);
7288
+ composer.selection.setSelection(range);
7307
7289
  },
7308
7290
 
7309
- state: function(element, command, tagName, className, classRegExp) {
7310
- var doc = element.ownerDocument,
7291
+ state: function(composer, command, tagName, className, classRegExp) {
7292
+ var doc = composer.doc,
7311
7293
  aliasTagName = ALIAS_MAPPING[tagName] || tagName,
7312
7294
  range;
7313
7295
 
@@ -7322,7 +7304,7 @@ wysihtml5.commands = {
7322
7304
  return false;
7323
7305
  }
7324
7306
 
7325
- range = wysihtml5.selection.getRange();
7307
+ range = composer.selection.getRange();
7326
7308
  if (!range) {
7327
7309
  return false;
7328
7310
  }
@@ -7338,11 +7320,11 @@ wysihtml5.commands = {
7338
7320
  var undef;
7339
7321
 
7340
7322
  wysihtml5.commands.insertHTML = {
7341
- exec: function(element, command, html) {
7342
- if (wysihtml5.commands.support(command)) {
7343
- element.ownerDocument.execCommand(command, false, html);
7323
+ exec: function(composer, command, html) {
7324
+ if (composer.commands.support(command)) {
7325
+ composer.doc.execCommand(command, false, html);
7344
7326
  } else {
7345
- wysihtml5.selection.insertHTML(html);
7327
+ composer.selection.insertHTML(html);
7346
7328
  }
7347
7329
  },
7348
7330
 
@@ -7364,33 +7346,34 @@ wysihtml5.commands = {
7364
7346
  *
7365
7347
  * @example
7366
7348
  * // either ...
7367
- * wysihtml5.commands.insertImage.exec(element, "insertImage", "http://www.google.de/logo.jpg");
7349
+ * wysihtml5.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg");
7368
7350
  * // ... or ...
7369
- * wysihtml5.commands.insertImage.exec(element, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" });
7351
+ * wysihtml5.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" });
7370
7352
  */
7371
- exec: function(element, command, value) {
7353
+ exec: function(composer, command, value) {
7372
7354
  value = typeof(value) === "object" ? value : { src: value };
7373
7355
 
7374
- var doc = element.ownerDocument,
7375
- image = this.state(element),
7356
+ var doc = composer.doc,
7357
+ image = this.state(composer),
7358
+ textNode,
7376
7359
  i,
7377
7360
  parent;
7378
7361
 
7379
7362
  if (image) {
7380
7363
  // Image already selected, set the caret before it and delete it
7381
- wysihtml5.selection.setBefore(image);
7364
+ composer.selection.setBefore(image);
7382
7365
  parent = image.parentNode;
7383
7366
  parent.removeChild(image);
7384
7367
 
7385
7368
  // and it's parent <a> too if it hasn't got any other relevant child nodes
7386
7369
  wysihtml5.dom.removeEmptyTextNodes(parent);
7387
7370
  if (parent.nodeName === "A" && !parent.firstChild) {
7388
- wysihtml5.selection.setAfter(parent);
7371
+ composer.selection.setAfter(parent);
7389
7372
  parent.parentNode.removeChild(parent);
7390
7373
  }
7391
7374
 
7392
7375
  // firefox and ie sometimes don't remove the image handles, even though the image got removed
7393
- wysihtml5.quirks.redraw(element);
7376
+ wysihtml5.quirks.redraw(composer.element);
7394
7377
  return;
7395
7378
  }
7396
7379
 
@@ -7400,12 +7383,18 @@ wysihtml5.commands = {
7400
7383
  image[i] = value[i];
7401
7384
  }
7402
7385
 
7403
- wysihtml5.selection.insertNode(image);
7404
- wysihtml5.selection.setAfter(image);
7386
+ composer.selection.insertNode(image);
7387
+ if (wysihtml5.browser.hasProblemsSettingCaretAfterImg()) {
7388
+ textNode = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
7389
+ composer.selection.insertNode(textNode);
7390
+ composer.selection.setAfter(textNode);
7391
+ } else {
7392
+ composer.selection.setAfter(image);
7393
+ }
7405
7394
  },
7406
7395
 
7407
- state: function(element) {
7408
- var doc = element.ownerDocument,
7396
+ state: function(composer) {
7397
+ var doc = composer.doc,
7409
7398
  selectedNode,
7410
7399
  text,
7411
7400
  imagesInSelection;
@@ -7414,7 +7403,7 @@ wysihtml5.commands = {
7414
7403
  return false;
7415
7404
  }
7416
7405
 
7417
- selectedNode = wysihtml5.selection.getSelectedNode();
7406
+ selectedNode = composer.selection.getSelectedNode();
7418
7407
  if (!selectedNode) {
7419
7408
  return false;
7420
7409
  }
@@ -7428,13 +7417,13 @@ wysihtml5.commands = {
7428
7417
  return false;
7429
7418
  }
7430
7419
 
7431
- text = wysihtml5.selection.getText();
7420
+ text = composer.selection.getText();
7432
7421
  text = wysihtml5.lang.string(text).trim();
7433
7422
  if (text) {
7434
7423
  return false;
7435
7424
  }
7436
7425
 
7437
- imagesInSelection = wysihtml5.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) {
7426
+ imagesInSelection = composer.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) {
7438
7427
  return node.nodeName === "IMG";
7439
7428
  });
7440
7429
 
@@ -7445,8 +7434,8 @@ wysihtml5.commands = {
7445
7434
  return imagesInSelection[0];
7446
7435
  },
7447
7436
 
7448
- value: function(element) {
7449
- var image = this.state(element);
7437
+ value: function(composer) {
7438
+ var image = this.state(composer);
7450
7439
  return image && image.src;
7451
7440
  }
7452
7441
  };
@@ -7455,14 +7444,14 @@ wysihtml5.commands = {
7455
7444
  LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
7456
7445
 
7457
7446
  wysihtml5.commands.insertLineBreak = {
7458
- exec: function(element, command) {
7459
- if (wysihtml5.commands.support(command)) {
7460
- element.ownerDocument.execCommand(command, false, null);
7447
+ exec: function(composer, command) {
7448
+ if (composer.commands.support(command)) {
7449
+ composer.doc.execCommand(command, false, null);
7461
7450
  if (!wysihtml5.browser.autoScrollsToCaret()) {
7462
- wysihtml5.selection.scrollIntoView(element);
7451
+ composer.selection.scrollIntoView();
7463
7452
  }
7464
7453
  } else {
7465
- wysihtml5.commands.exec("insertHTML", LINE_BREAK);
7454
+ composer.commands.exec("insertHTML", LINE_BREAK);
7466
7455
  }
7467
7456
  },
7468
7457
 
@@ -7478,33 +7467,33 @@ wysihtml5.commands = {
7478
7467
  var undef;
7479
7468
 
7480
7469
  wysihtml5.commands.insertOrderedList = {
7481
- exec: function(element, command) {
7482
- var doc = element.ownerDocument,
7470
+ exec: function(composer, command) {
7471
+ var doc = composer.doc,
7483
7472
  selectedNode,
7484
7473
  isEmpty,
7485
7474
  tempElement,
7486
7475
  list;
7487
7476
 
7488
- if (wysihtml5.commands.support(command)) {
7477
+ if (composer.commands.support(command)) {
7489
7478
  doc.execCommand(command, false, null);
7490
7479
  } else {
7491
- selectedNode = wysihtml5.selection.getSelectedNode();
7480
+ selectedNode = composer.selection.getSelectedNode();
7492
7481
  list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: ["UL", "OL"] }, 4);
7493
7482
  if (!list) {
7494
7483
  tempElement = doc.createElement("span");
7495
- wysihtml5.selection.surround(tempElement);
7484
+ composer.selection.surround(tempElement);
7496
7485
  isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE;
7497
- wysihtml5.selection.executeAndRestoreSimple(function() {
7486
+ composer.selection.executeAndRestoreSimple(function() {
7498
7487
  list = wysihtml5.dom.convertToList(tempElement, "ol");
7499
7488
  });
7500
7489
 
7501
7490
  if (isEmpty) {
7502
- wysihtml5.selection.selectNode(list.querySelector("li"));
7491
+ composer.selection.selectNode(list.querySelector("li"));
7503
7492
  }
7504
7493
  return;
7505
7494
  }
7506
7495
 
7507
- wysihtml5.selection.executeAndRestoreSimple(function() {
7496
+ composer.selection.executeAndRestoreSimple(function() {
7508
7497
  if (list.nodeName === "OL") {
7509
7498
  // Unwrap list
7510
7499
  // <ol><li>foo</li><li>bar</li></ol>
@@ -7522,9 +7511,9 @@ wysihtml5.commands = {
7522
7511
  }
7523
7512
  },
7524
7513
 
7525
- state: function(element, command) {
7514
+ state: function(composer, command) {
7526
7515
  try {
7527
- return element.ownerDocument.queryCommandState(command);
7516
+ return composer.doc.queryCommandState(command);
7528
7517
  } catch(e) {
7529
7518
  return false;
7530
7519
  }
@@ -7538,34 +7527,34 @@ wysihtml5.commands = {
7538
7527
  var undef;
7539
7528
 
7540
7529
  wysihtml5.commands.insertUnorderedList = {
7541
- exec: function(element, command) {
7542
- var doc = element.ownerDocument,
7530
+ exec: function(composer, command) {
7531
+ var doc = composer.doc,
7543
7532
  selectedNode,
7544
7533
  isEmpty,
7545
7534
  tempElement,
7546
7535
  list;
7547
7536
 
7548
- if (wysihtml5.commands.support(command)) {
7537
+ if (composer.commands.support(command)) {
7549
7538
  doc.execCommand(command, false, null);
7550
7539
  } else {
7551
- selectedNode = wysihtml5.selection.getSelectedNode();
7540
+ selectedNode = composer.selection.getSelectedNode();
7552
7541
  list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: ["UL", "OL"] });
7553
7542
 
7554
7543
  if (!list) {
7555
7544
  tempElement = doc.createElement("span");
7556
- wysihtml5.selection.surround(tempElement);
7545
+ composer.selection.surround(tempElement);
7557
7546
  isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE;
7558
- wysihtml5.selection.executeAndRestoreSimple(function() {
7547
+ composer.selection.executeAndRestoreSimple(function() {
7559
7548
  list = wysihtml5.dom.convertToList(tempElement, "ul");
7560
7549
  });
7561
7550
 
7562
7551
  if (isEmpty) {
7563
- wysihtml5.selection.selectNode(list.querySelector("li"));
7552
+ composer.selection.selectNode(list.querySelector("li"));
7564
7553
  }
7565
7554
  return;
7566
7555
  }
7567
7556
 
7568
- wysihtml5.selection.executeAndRestoreSimple(function() {
7557
+ composer.selection.executeAndRestoreSimple(function() {
7569
7558
  if (list.nodeName === "UL") {
7570
7559
  // Unwrap list
7571
7560
  // <ul><li>foo</li><li>bar</li></ul>
@@ -7583,9 +7572,9 @@ wysihtml5.commands = {
7583
7572
  }
7584
7573
  },
7585
7574
 
7586
- state: function(element, command) {
7575
+ state: function(composer, command) {
7587
7576
  try {
7588
- return element.ownerDocument.queryCommandState(command);
7577
+ return composer.doc.queryCommandState(command);
7589
7578
  } catch(e) {
7590
7579
  return false;
7591
7580
  }
@@ -7599,17 +7588,17 @@ wysihtml5.commands = {
7599
7588
  var undef;
7600
7589
 
7601
7590
  wysihtml5.commands.italic = {
7602
- exec: function(element, command) {
7603
- return wysihtml5.commands.formatInline.exec(element, command, "i");
7591
+ exec: function(composer, command) {
7592
+ return wysihtml5.commands.formatInline.exec(composer, command, "i");
7604
7593
  },
7605
7594
 
7606
- state: function(element, command, color) {
7595
+ state: function(composer, command, color) {
7607
7596
  // element.ownerDocument.queryCommandState("italic") results:
7608
7597
  // firefox: only <i>
7609
7598
  // chrome: <i>, <em>, <blockquote>, ...
7610
7599
  // ie: <i>, <em>
7611
7600
  // opera: only <i>
7612
- return wysihtml5.commands.formatInline.state(element, command, "i");
7601
+ return wysihtml5.commands.formatInline.state(composer, command, "i");
7613
7602
  },
7614
7603
 
7615
7604
  value: function() {
@@ -7622,12 +7611,12 @@ wysihtml5.commands = {
7622
7611
  REG_EXP = /wysiwyg-text-align-[a-z]+/g;
7623
7612
 
7624
7613
  wysihtml5.commands.justifyCenter = {
7625
- exec: function(element, command) {
7626
- return wysihtml5.commands.formatBlock.exec(element, "formatBlock", null, CLASS_NAME, REG_EXP);
7614
+ exec: function(composer, command) {
7615
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
7627
7616
  },
7628
7617
 
7629
- state: function(element, command) {
7630
- return wysihtml5.commands.formatBlock.state(element, "formatBlock", null, CLASS_NAME, REG_EXP);
7618
+ state: function(composer, command) {
7619
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
7631
7620
  },
7632
7621
 
7633
7622
  value: function() {
@@ -7640,12 +7629,12 @@ wysihtml5.commands = {
7640
7629
  REG_EXP = /wysiwyg-text-align-[a-z]+/g;
7641
7630
 
7642
7631
  wysihtml5.commands.justifyLeft = {
7643
- exec: function(element, command) {
7644
- return wysihtml5.commands.formatBlock.exec(element, "formatBlock", null, CLASS_NAME, REG_EXP);
7632
+ exec: function(composer, command) {
7633
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
7645
7634
  },
7646
7635
 
7647
- state: function(element, command) {
7648
- return wysihtml5.commands.formatBlock.state(element, "formatBlock", null, CLASS_NAME, REG_EXP);
7636
+ state: function(composer, command) {
7637
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
7649
7638
  },
7650
7639
 
7651
7640
  value: function() {
@@ -7658,12 +7647,12 @@ wysihtml5.commands = {
7658
7647
  REG_EXP = /wysiwyg-text-align-[a-z]+/g;
7659
7648
 
7660
7649
  wysihtml5.commands.justifyRight = {
7661
- exec: function(element, command) {
7662
- return wysihtml5.commands.formatBlock.exec(element, "formatBlock", null, CLASS_NAME, REG_EXP);
7650
+ exec: function(composer, command) {
7651
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
7663
7652
  },
7664
7653
 
7665
- state: function(element, command) {
7666
- return wysihtml5.commands.formatBlock.state(element, "formatBlock", null, CLASS_NAME, REG_EXP);
7654
+ state: function(composer, command) {
7655
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
7667
7656
  },
7668
7657
 
7669
7658
  value: function() {
@@ -7676,12 +7665,12 @@ wysihtml5.commands = {
7676
7665
  CLASS_NAME = "wysiwyg-text-decoration-underline";
7677
7666
 
7678
7667
  wysihtml5.commands.underline = {
7679
- exec: function(element, command) {
7680
- return wysihtml5.commands.formatInline.exec(element, command, "span", CLASS_NAME, REG_EXP);
7668
+ exec: function(composer, command) {
7669
+ return wysihtml5.commands.formatInline.exec(composer, command, "span", CLASS_NAME, REG_EXP);
7681
7670
  },
7682
7671
 
7683
- state: function(element, command) {
7684
- return wysihtml5.commands.formatInline.state(element, command, "span", CLASS_NAME, REG_EXP);
7672
+ state: function(composer, command) {
7673
+ return wysihtml5.commands.formatInline.state(composer, command, "span", CLASS_NAME, REG_EXP);
7685
7674
  },
7686
7675
 
7687
7676
  value: function() {
@@ -7713,24 +7702,25 @@ wysihtml5.commands = {
7713
7702
  /** @scope wysihtml5.UndoManager.prototype */ {
7714
7703
  constructor: function(editor) {
7715
7704
  this.editor = editor;
7716
- this.composerElement = editor.composer.element;
7717
- this.history = [this.editor.composer.getValue()];
7705
+ this.composer = editor.composer;
7706
+ this.element = this.composer.element;
7707
+ this.history = [this.composer.getValue()];
7718
7708
  this.position = 1;
7719
7709
 
7720
7710
  // Undo manager currently only supported in browsers who have the insertHTML command (not IE)
7721
- if (wysihtml5.commands.support("insertHTML")) {
7711
+ if (this.composer.commands.support("insertHTML")) {
7722
7712
  this._observe();
7723
7713
  }
7724
7714
  },
7725
7715
 
7726
7716
  _observe: function() {
7727
- var that = this,
7728
- doc = this.editor.composer.sandbox.getDocument(),
7717
+ var that = this,
7718
+ doc = this.composer.sandbox.getDocument(),
7729
7719
  lastKey;
7730
7720
 
7731
7721
  // Catch CTRL+Z and CTRL+Y
7732
- dom.observe(this.composerElement, "keydown", function(event) {
7733
- if (!event.ctrlKey && !event.metaKey) {
7722
+ dom.observe(this.element, "keydown", function(event) {
7723
+ if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
7734
7724
  return;
7735
7725
  }
7736
7726
 
@@ -7748,7 +7738,7 @@ wysihtml5.commands = {
7748
7738
  });
7749
7739
 
7750
7740
  // Catch delete and backspace
7751
- dom.observe(this.composerElement, "keydown", function(event) {
7741
+ dom.observe(this.element, "keydown", function(event) {
7752
7742
  var keyCode = event.keyCode;
7753
7743
  if (keyCode === lastKey) {
7754
7744
  return;
@@ -7761,12 +7751,6 @@ wysihtml5.commands = {
7761
7751
  }
7762
7752
  });
7763
7753
 
7764
- var interval, observed, cleanUp = function() {
7765
- cleanTempElements(doc);
7766
- clearInterval(interval);
7767
- };
7768
-
7769
-
7770
7754
  // Now this is very hacky:
7771
7755
  // These days browsers don't offer a undo/redo event which we could hook into
7772
7756
  // to be notified when the user hits undo/redo in the contextmenu.
@@ -7774,36 +7758,43 @@ wysihtml5.commands = {
7774
7758
  // The last element being inserted will be immediately be removed again by a exexCommand("undo")
7775
7759
  // => When the second element appears in the dom tree then we know the user clicked "redo" in the context menu
7776
7760
  // => When the first element disappears from the dom tree then we know the user clicked "undo" in the context menu
7777
- dom.observe(this.composerElement, "contextmenu", function() {
7778
- cleanUp();
7779
- wysihtml5.selection.executeAndRestoreSimple(function() {
7780
- if (that.composerElement.lastChild) {
7781
- wysihtml5.selection.setAfter(that.composerElement.lastChild);
7782
- }
7783
-
7784
- // enable undo button in context menu
7785
- doc.execCommand("insertHTML", false, UNDO_HTML);
7786
- // enable redo button in context menu
7787
- doc.execCommand("insertHTML", false, REDO_HTML);
7788
- doc.execCommand("undo", false, null);
7789
- });
7761
+ if (wysihtml5.browser.hasUndoInContextMenu()) {
7762
+ var interval, observed, cleanUp = function() {
7763
+ cleanTempElements(doc);
7764
+ clearInterval(interval);
7765
+ };
7790
7766
 
7791
- interval = setInterval(function() {
7792
- if (doc.getElementById("_wysihtml5-redo")) {
7793
- cleanUp();
7794
- that.redo();
7795
- } else if (!doc.getElementById("_wysihtml5-undo")) {
7796
- cleanUp();
7797
- that.undo();
7767
+ dom.observe(this.element, "contextmenu", function() {
7768
+ cleanUp();
7769
+ that.composer.selection.executeAndRestoreSimple(function() {
7770
+ if (that.element.lastChild) {
7771
+ that.composer.selection.setAfter(that.element.lastChild);
7772
+ }
7773
+
7774
+ // enable undo button in context menu
7775
+ doc.execCommand("insertHTML", false, UNDO_HTML);
7776
+ // enable redo button in context menu
7777
+ doc.execCommand("insertHTML", false, REDO_HTML);
7778
+ doc.execCommand("undo", false, null);
7779
+ });
7780
+
7781
+ interval = setInterval(function() {
7782
+ if (doc.getElementById("_wysihtml5-redo")) {
7783
+ cleanUp();
7784
+ that.redo();
7785
+ } else if (!doc.getElementById("_wysihtml5-undo")) {
7786
+ cleanUp();
7787
+ that.undo();
7788
+ }
7789
+ }, 400);
7790
+
7791
+ if (!observed) {
7792
+ observed = true;
7793
+ dom.observe(document, "mousedown", cleanUp);
7794
+ dom.observe(doc, ["mousedown", "paste", "cut", "copy"], cleanUp);
7798
7795
  }
7799
- }, 400);
7800
-
7801
- if (!observed) {
7802
- observed = true;
7803
- dom.observe(document, "mousedown", cleanUp);
7804
- dom.observe(doc, ["mousedown", "paste", "cut", "copy"], cleanUp);
7805
- }
7806
- });
7796
+ });
7797
+ }
7807
7798
 
7808
7799
  this.editor
7809
7800
  .observe("newword:composer", function() {
@@ -7817,7 +7808,7 @@ wysihtml5.commands = {
7817
7808
 
7818
7809
  transact: function() {
7819
7810
  var previousHtml = this.history[this.position - 1],
7820
- currentHtml = this.editor.composer.getValue();
7811
+ currentHtml = this.composer.getValue();
7821
7812
 
7822
7813
  if (currentHtml == previousHtml) {
7823
7814
  return;
@@ -7854,7 +7845,7 @@ wysihtml5.commands = {
7854
7845
  },
7855
7846
 
7856
7847
  set: function(html) {
7857
- this.editor.composer.setValue(html);
7848
+ this.composer.setValue(html);
7858
7849
  this.editor.focus(true);
7859
7850
  }
7860
7851
  });
@@ -7913,9 +7904,7 @@ wysihtml5.views.View = Base.extend(
7913
7904
  }
7914
7905
  });(function(wysihtml5) {
7915
7906
  var dom = wysihtml5.dom,
7916
- browser = wysihtml5.browser,
7917
- selection = wysihtml5.selection,
7918
- commands = wysihtml5.commands;
7907
+ browser = wysihtml5.browser;
7919
7908
 
7920
7909
  wysihtml5.views.Composer = wysihtml5.views.View.extend(
7921
7910
  /** @scope wysihtml5.views.Composer.prototype */ {
@@ -7983,14 +7972,21 @@ wysihtml5.views.View = Base.extend(
7983
7972
  },
7984
7973
 
7985
7974
  focus: function(setToEnd) {
7975
+ // IE 8 fires the focus event after .focus()
7976
+ // This is needed by our simulate_placeholder.js to work
7977
+ // therefore we clear it ourselves this time
7978
+ if (wysihtml5.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
7979
+ this.clear();
7980
+ }
7981
+
7986
7982
  this.base();
7987
7983
 
7988
7984
  var lastChild = this.element.lastChild;
7989
7985
  if (setToEnd && lastChild) {
7990
7986
  if (lastChild.nodeName === "BR") {
7991
- selection.setBefore(this.element.lastChild);
7987
+ this.selection.setBefore(this.element.lastChild);
7992
7988
  } else {
7993
- selection.setAfter(this.element.lastChild);
7989
+ this.selection.setAfter(this.element.lastChild);
7994
7990
  }
7995
7991
  }
7996
7992
  },
@@ -8018,8 +8014,7 @@ wysihtml5.views.View = Base.extend(
8018
8014
  this.sandbox = new dom.Sandbox(function() {
8019
8015
  that._create();
8020
8016
  }, {
8021
- stylesheets: this.config.stylesheets,
8022
- uaCompatible: "IE=7"
8017
+ stylesheets: this.config.stylesheets
8023
8018
  });
8024
8019
  this.iframe = this.sandbox.getIframe();
8025
8020
 
@@ -8038,16 +8033,17 @@ wysihtml5.views.View = Base.extend(
8038
8033
  _create: function() {
8039
8034
  var that = this;
8040
8035
 
8041
- this.element = this.sandbox.getDocument().body;
8036
+ this.doc = this.sandbox.getDocument();
8037
+ this.element = this.doc.body;
8042
8038
  this.textarea = this.parent.textarea;
8043
8039
  this.element.innerHTML = this.textarea.getValue(true);
8044
8040
  this.enable();
8045
8041
 
8046
8042
  // Make sure our selection handler is ready
8047
- selection.initialize(this.sandbox.getDocument());
8043
+ this.selection = new wysihtml5.Selection(this.parent);
8048
8044
 
8049
- // Make sure commands are ready
8050
- commands.initialize(this.parent);
8045
+ // Make sure commands dispatcher is ready
8046
+ this.commands = new wysihtml5.Commands(this.parent);
8051
8047
 
8052
8048
  dom.copyAttributes([
8053
8049
  "className", "spellcheck", "title", "lang", "dir", "accessKey"
@@ -8077,7 +8073,7 @@ wysihtml5.views.View = Base.extend(
8077
8073
  }
8078
8074
 
8079
8075
  // Make sure that the browser avoids using inline styles whenever possible
8080
- commands.exec("styleWithCSS", false);
8076
+ this.commands.exec("styleWithCSS", false);
8081
8077
 
8082
8078
  this._initAutoLinking();
8083
8079
  this._initObjectResizing();
@@ -8088,15 +8084,15 @@ wysihtml5.views.View = Base.extend(
8088
8084
  setTimeout(function() { that.focus(); }, 100);
8089
8085
  }
8090
8086
 
8091
- wysihtml5.quirks.insertLineBreakOnReturn(this.element);
8087
+ wysihtml5.quirks.insertLineBreakOnReturn(this);
8092
8088
 
8093
8089
  // IE sometimes leaves a single paragraph, which can't be removed by the user
8094
8090
  if (!browser.clearsContentEditableCorrectly()) {
8095
- wysihtml5.quirks.ensureProperClearing(this.element);
8091
+ wysihtml5.quirks.ensureProperClearing(this);
8096
8092
  }
8097
8093
 
8098
8094
  if (!browser.clearsListsInContentEditableCorrectly()) {
8099
- wysihtml5.quirks.ensureProperClearingOfLists(this.element);
8095
+ wysihtml5.quirks.ensureProperClearingOfLists(this);
8100
8096
  }
8101
8097
 
8102
8098
  // Set up a sync that makes sure that textarea and editor have the same content
@@ -8112,10 +8108,11 @@ wysihtml5.views.View = Base.extend(
8112
8108
  },
8113
8109
 
8114
8110
  _initAutoLinking: function() {
8115
- var supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
8111
+ var that = this,
8112
+ supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
8116
8113
  supportsAutoLinking = browser.doesAutoLinkingInContentEditable();
8117
8114
  if (supportsDisablingOfAutoLinking) {
8118
- commands.exec("autoUrlDetect", false);
8115
+ this.commands.exec("autoUrlDetect", false);
8119
8116
  }
8120
8117
 
8121
8118
  if (!this.config.autoLink) {
@@ -8126,7 +8123,7 @@ wysihtml5.views.View = Base.extend(
8126
8123
  // OR when he supports auto linking but we were able to turn it off (IE9+)
8127
8124
  if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
8128
8125
  this.parent.observe("newword:composer", function() {
8129
- selection.executeAndRestore(function(startContainer, endContainer) {
8126
+ that.selection.executeAndRestore(function(startContainer, endContainer) {
8130
8127
  dom.autoLink(endContainer.parentNode);
8131
8128
  });
8132
8129
  });
@@ -8153,7 +8150,7 @@ wysihtml5.views.View = Base.extend(
8153
8150
  return;
8154
8151
  }
8155
8152
 
8156
- var selectedNode = selection.getSelectedNode(event.target.ownerDocument),
8153
+ var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
8157
8154
  link = dom.getParentElement(selectedNode, { nodeName: "A" }, 4),
8158
8155
  textContent;
8159
8156
 
@@ -8183,8 +8180,8 @@ wysihtml5.views.View = Base.extend(
8183
8180
  propertiesLength = properties.length,
8184
8181
  element = this.element;
8185
8182
 
8186
- commands.exec("enableObjectResizing", this.config.allowObjectResizing);
8187
-
8183
+ this.commands.exec("enableObjectResizing", this.config.allowObjectResizing);
8184
+
8188
8185
  if (this.config.allowObjectResizing) {
8189
8186
  // IE sets inline styles after resizing objects
8190
8187
  // The following lines make sure that the width/height css properties
@@ -8406,8 +8403,6 @@ wysihtml5.views.View = Base.extend(
8406
8403
  * - Catch paste events
8407
8404
  * - Dispatch proprietary newword:composer event
8408
8405
  * - Keyboard shortcuts
8409
- *
8410
- * @author Christopher Blum <christopher.blum@xing.com>
8411
8406
  */
8412
8407
  (function(wysihtml5) {
8413
8408
  var dom = wysihtml5.dom,
@@ -8470,7 +8465,7 @@ wysihtml5.views.View = Base.extend(
8470
8465
  originalScrollTop = document.documentElement.scrollTop || document.body.scrollTop,
8471
8466
  originalScrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
8472
8467
  try {
8473
- wysihtml5.selection.insertNode(input);
8468
+ that.selection.insertNode(input);
8474
8469
  } catch(e) {
8475
8470
  element.appendChild(input);
8476
8471
  }
@@ -8501,7 +8496,7 @@ wysihtml5.views.View = Base.extend(
8501
8496
  }
8502
8497
  if (data) {
8503
8498
  element.focus();
8504
- wysihtml5.commands.exec("insertHTML", data);
8499
+ that.commands.exec("insertHTML", data);
8505
8500
  that.parent.fire("paste").fire("paste:composer");
8506
8501
  event.stopPropagation();
8507
8502
  event.preventDefault();
@@ -8529,7 +8524,7 @@ wysihtml5.views.View = Base.extend(
8529
8524
  dom.observe(element, "mousedown", function(event) {
8530
8525
  var target = event.target;
8531
8526
  if (target.nodeName === "IMG") {
8532
- wysihtml5.selection.selectNode(target);
8527
+ that.selection.selectNode(target);
8533
8528
  event.preventDefault();
8534
8529
  }
8535
8530
  });
@@ -8540,14 +8535,14 @@ wysihtml5.views.View = Base.extend(
8540
8535
  var keyCode = event.keyCode,
8541
8536
  command = shortcuts[keyCode];
8542
8537
  if ((event.ctrlKey || event.metaKey) && command) {
8543
- wysihtml5.commands.exec(command);
8538
+ that.commands.exec(command);
8544
8539
  event.preventDefault();
8545
8540
  }
8546
8541
  });
8547
8542
 
8548
8543
  // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
8549
8544
  dom.observe(element, "keydown", function(event) {
8550
- var target = wysihtml5.selection.getSelectedNode(true),
8545
+ var target = that.selection.getSelectedNode(true),
8551
8546
  keyCode = event.keyCode,
8552
8547
  parent;
8553
8548
  if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete
@@ -8747,7 +8742,6 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
8747
8742
  });/**
8748
8743
  * Toolbar Dialog
8749
8744
  *
8750
- * @author Christopher Blum <christopher.blum@xing.com>
8751
8745
  * @param {Element} link The toolbar link which causes the dialog to show up
8752
8746
  * @param {Element} container The dialog container
8753
8747
  *
@@ -8877,7 +8871,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
8877
8871
  * Basically it adopted the attribute values into the corresponding input fields
8878
8872
  *
8879
8873
  */
8880
- _interpolate: function() {
8874
+ _interpolate: function(avoidHiddenFields) {
8881
8875
  var field,
8882
8876
  fieldName,
8883
8877
  newValue,
@@ -8887,10 +8881,18 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
8887
8881
  i = 0;
8888
8882
  for (; i<length; i++) {
8889
8883
  field = fields[i];
8884
+
8890
8885
  // Never change elements where the user is currently typing in
8891
8886
  if (field === focusedElement) {
8892
8887
  continue;
8893
8888
  }
8889
+
8890
+ // Don't update hidden fields
8891
+ // See https://github.com/xing/wysihtml5/pull/14
8892
+ if (avoidHiddenFields && field.type === "hidden") {
8893
+ continue;
8894
+ }
8895
+
8894
8896
  fieldName = field.getAttribute(ATTRIBUTE_FIELDS);
8895
8897
  newValue = this.elementToChange ? (this.elementToChange[fieldName] || "") : field.defaultValue;
8896
8898
  field.value = newValue;
@@ -8907,7 +8909,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
8907
8909
  this._observe();
8908
8910
  this._interpolate();
8909
8911
  if (elementToChange) {
8910
- this.interval = setInterval(function() { that._interpolate(); }, 500);
8912
+ this.interval = setInterval(function() { that._interpolate(true); }, 500);
8911
8913
  }
8912
8914
  dom.addClass(this.link, CLASS_NAME_OPENED);
8913
8915
  this.container.style.display = "";
@@ -8943,8 +8945,6 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
8943
8945
  *
8944
8946
  * "Accessing Google Speech API Chrome 11"
8945
8947
  * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
8946
- *
8947
- * @author Christopher Blum <christopher.blum@xing.com>
8948
8948
  */
8949
8949
  (function(wysihtml5) {
8950
8950
  var dom = wysihtml5.dom;
@@ -9021,7 +9021,6 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
9021
9021
  })(wysihtml5);/**
9022
9022
  * Toolbar
9023
9023
  *
9024
- * @author Christopher Blum <christopher.blum@xing.com>
9025
9024
  * @param {Object} parent Reference to instance of Editor instance
9026
9025
  * @param {Element} container Reference to the toolbar container element
9027
9026
  *
@@ -9092,20 +9091,19 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
9092
9091
  dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
9093
9092
  dialog,
9094
9093
  caretBookmark;
9094
+
9095
9095
  if (dialogElement) {
9096
9096
  dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
9097
9097
 
9098
9098
  dialog.observe("show", function() {
9099
- caretBookmark = wysihtml5.selection.getBookmark();
9099
+ caretBookmark = that.composer.selection.getBookmark();
9100
9100
 
9101
9101
  that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
9102
9102
  });
9103
9103
 
9104
9104
  dialog.observe("save", function(attributes) {
9105
- that.editor.focus(false);
9106
-
9107
9105
  if (caretBookmark) {
9108
- wysihtml5.selection.setBookmark(caretBookmark);
9106
+ that.composer.selection.setBookmark(caretBookmark);
9109
9107
  }
9110
9108
  that._execCommand(command, attributes);
9111
9109
 
@@ -9145,7 +9143,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
9145
9143
  // Make sure that composer is focussed (false => don't move caret to the end)
9146
9144
  this.editor.focus(false);
9147
9145
 
9148
- wysihtml5.commands.exec(command, commandValue);
9146
+ this.composer.commands.exec(command, commandValue);
9149
9147
  this._updateLinkStates();
9150
9148
  },
9151
9149
 
@@ -9240,7 +9238,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
9240
9238
  command.dialog.hide();
9241
9239
  }
9242
9240
  } else {
9243
- state = wysihtml5.commands.state(command.name, command.value);
9241
+ state = this.composer.commands.state(command.name, command.value);
9244
9242
  if (wysihtml5.lang.object(state).isArray()) {
9245
9243
  // Grab first and only object/element in state array, otherwise convert state into boolean
9246
9244
  // to avoid showing a dialog for multiple selected elements which may have different attributes
@@ -9287,7 +9285,6 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
9287
9285
  /**
9288
9286
  * WYSIHTML5 Editor
9289
9287
  *
9290
- * @author Christopher Blum <christopher.blum@xing.com>
9291
9288
  * @param {Element} textareaElement Reference to the textarea which should be turned into a rich text interface
9292
9289
  * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
9293
9290
  *
@@ -9449,7 +9446,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
9449
9446
  this.observe("paste:composer", function() {
9450
9447
  var keepScrollPosition = true,
9451
9448
  that = this;
9452
- wysihtml5.selection.executeAndRestore(function() {
9449
+ that.composer.selection.executeAndRestore(function() {
9453
9450
  wysihtml5.quirks.cleanPastedHTML(that.composer.element);
9454
9451
  that.parse(that.composer.element);
9455
9452
  }, keepScrollPosition);