wysihtml5x-rails 0.4.13 → 0.4.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,7 +25,7 @@ if(!Array.isArray) {
25
25
  return Object.prototype.toString.call(arg) === '[object Array]';
26
26
  };
27
27
  };/**
28
- * @license wysihtml5x v0.4.13
28
+ * @license wysihtml5x v0.4.14
29
29
  * https://github.com/Edicy/wysihtml5
30
30
  *
31
31
  * Author: Christopher Blum (https://github.com/tiff)
@@ -36,7 +36,7 @@ if(!Array.isArray) {
36
36
  *
37
37
  */
38
38
  var wysihtml5 = {
39
- version: "0.4.13",
39
+ version: "0.4.14",
40
40
 
41
41
  // namespaces
42
42
  commands: {},
@@ -4562,6 +4562,15 @@ wysihtml5.browser = (function() {
4562
4562
 
4563
4563
  supportsMutationEvents: function() {
4564
4564
  return ("MutationEvent" in window);
4565
+ },
4566
+
4567
+ /**
4568
+ IE (at least up to 11) does not support clipboardData on event.
4569
+ It is on window but cannot return text/html
4570
+ Should actually check for clipboardData on paste event, but cannot in firefox
4571
+ */
4572
+ supportsModenPaste: function () {
4573
+ return !("clipboardData" in window);
4565
4574
  }
4566
4575
  };
4567
4576
  })();
@@ -4763,12 +4772,25 @@ wysihtml5.browser = (function() {
4763
4772
  * @example
4764
4773
  * wysihtml5.lang.object({ foo: 1 }).clone();
4765
4774
  * // => { foo: 1 }
4775
+ *
4776
+ * v0.4.14 adds options for deep clone : wysihtml5.lang.object({ foo: 1 }).clone(true);
4766
4777
  */
4767
- clone: function() {
4778
+ clone: function(deep) {
4768
4779
  var newObj = {},
4769
4780
  i;
4781
+
4782
+ if (obj === null || !wysihtml5.lang.object(obj).isPlainObject()) {
4783
+ return obj;
4784
+ }
4785
+
4770
4786
  for (i in obj) {
4771
- newObj[i] = obj[i];
4787
+ if(obj.hasOwnProperty(i)) {
4788
+ if (deep) {
4789
+ newObj[i] = wysihtml5.lang.object(obj[i]).clone(deep);
4790
+ } else {
4791
+ newObj[i] = obj[i];
4792
+ }
4793
+ }
4772
4794
  }
4773
4795
  return newObj;
4774
4796
  },
@@ -4780,18 +4802,32 @@ wysihtml5.browser = (function() {
4780
4802
  */
4781
4803
  isArray: function() {
4782
4804
  return Object.prototype.toString.call(obj) === "[object Array]";
4805
+ },
4806
+
4807
+ /**
4808
+ * @example
4809
+ * wysihtml5.lang.object(function() {}).isFunction();
4810
+ * // => true
4811
+ */
4812
+ isFunction: function() {
4813
+ return Object.prototype.toString.call(obj) === '[object Function]';
4814
+ },
4815
+
4816
+ isPlainObject: function () {
4817
+ return Object.prototype.toString.call(obj) === '[object Object]';
4783
4818
  }
4784
4819
  };
4785
4820
  };
4786
4821
  ;(function() {
4787
4822
  var WHITE_SPACE_START = /^\s+/,
4788
4823
  WHITE_SPACE_END = /\s+$/,
4789
- ENTITY_REG_EXP = /[&<>"]/g,
4824
+ ENTITY_REG_EXP = /[&<>\t"]/g,
4790
4825
  ENTITY_MAP = {
4791
4826
  '&': '&amp;',
4792
4827
  '<': '&lt;',
4793
4828
  '>': '&gt;',
4794
- '"': "&quot;"
4829
+ '"': "&quot;",
4830
+ '\t':"&nbsp; "
4795
4831
  };
4796
4832
  wysihtml5.lang.string = function(str) {
4797
4833
  str = String(str);
@@ -4835,8 +4871,15 @@ wysihtml5.browser = (function() {
4835
4871
  * wysihtml5.lang.string("hello<br>").escapeHTML();
4836
4872
  * // => "hello&lt;br&gt;"
4837
4873
  */
4838
- escapeHTML: function() {
4839
- return str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
4874
+ escapeHTML: function(linebreaks, convertSpaces) {
4875
+ var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
4876
+ if (linebreaks) {
4877
+ html = html.replace(/(?:\r\n|\r|\n)/g, '<br />');
4878
+ }
4879
+ if (convertSpaces) {
4880
+ html = html.replace(/ /gi, "&nbsp; ");
4881
+ }
4882
+ return html;
4840
4883
  }
4841
4884
  };
4842
4885
  };
@@ -5816,7 +5859,9 @@ wysihtml5.dom.observe = function(element, eventNames, handler) {
5816
5859
  * // => '<p class="red">foo</p><p>bar</p>'
5817
5860
  */
5818
5861
 
5819
- wysihtml5.dom.parse = (function() {
5862
+ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
5863
+ /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors.
5864
+ * Refactor whole code as this method while workind is kind of awkward too */
5820
5865
 
5821
5866
  /**
5822
5867
  * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
@@ -5834,8 +5879,7 @@ wysihtml5.dom.parse = (function() {
5834
5879
  DEFAULT_NODE_NAME = "span",
5835
5880
  WHITE_SPACE_REG_EXP = /\s+/,
5836
5881
  defaultRules = { tags: {}, classes: {} },
5837
- currentRules = {},
5838
- uneditableClass = false;
5882
+ currentRules = {};
5839
5883
 
5840
5884
  /**
5841
5885
  * Iterates over all childs of the element, recreates them, appends them into a document fragment
@@ -5856,19 +5900,19 @@ wysihtml5.dom.parse = (function() {
5856
5900
  clearInternals = true;
5857
5901
  }
5858
5902
 
5859
- if (config.uneditableClass) {
5860
- uneditableClass = config.uneditableClass;
5861
- }
5862
-
5863
5903
  if (isString) {
5864
5904
  element = wysihtml5.dom.getAsDom(elementOrHtml, context);
5865
5905
  } else {
5866
5906
  element = elementOrHtml;
5867
5907
  }
5868
5908
 
5909
+ if (currentRules.selectors) {
5910
+ _applySelectorRules(element, currentRules.selectors);
5911
+ }
5912
+
5869
5913
  while (element.firstChild) {
5870
5914
  firstChild = element.firstChild;
5871
- newNode = _convert(firstChild, config.cleanUp, clearInternals);
5915
+ newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass);
5872
5916
  if (newNode) {
5873
5917
  fragment.appendChild(newNode);
5874
5918
  }
@@ -5877,6 +5921,14 @@ wysihtml5.dom.parse = (function() {
5877
5921
  }
5878
5922
  }
5879
5923
 
5924
+ if (config.unjoinNbsps) {
5925
+ // replace joined non-breakable spaces with unjoined
5926
+ var txtnodes = wysihtml5.dom.getTextNodes(fragment);
5927
+ for (var n = txtnodes.length; n--;) {
5928
+ txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
5929
+ }
5930
+ }
5931
+
5880
5932
  // Clear element contents
5881
5933
  element.innerHTML = "";
5882
5934
 
@@ -5886,7 +5938,7 @@ wysihtml5.dom.parse = (function() {
5886
5938
  return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
5887
5939
  }
5888
5940
 
5889
- function _convert(oldNode, cleanUp, clearInternals) {
5941
+ function _convert(oldNode, cleanUp, clearInternals, uneditableClass) {
5890
5942
  var oldNodeType = oldNode.nodeType,
5891
5943
  oldChilds = oldNode.childNodes,
5892
5944
  oldChildsLength = oldChilds.length,
@@ -5911,7 +5963,7 @@ wysihtml5.dom.parse = (function() {
5911
5963
 
5912
5964
  for (i = oldChildsLength; i--;) {
5913
5965
  if (oldChilds[i]) {
5914
- newChild = _convert(oldChilds[i], cleanUp, clearInternals);
5966
+ newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
5915
5967
  if (newChild) {
5916
5968
  if (oldChilds[i] === newChild) {
5917
5969
  i--;
@@ -5921,6 +5973,10 @@ wysihtml5.dom.parse = (function() {
5921
5973
  }
5922
5974
  }
5923
5975
 
5976
+ if (wysihtml5.dom.getStyle("display").from(oldNode) === "block") {
5977
+ fragment.appendChild(oldNode.ownerDocument.createElement("br"));
5978
+ }
5979
+
5924
5980
  // TODO: try to minimize surplus spaces
5925
5981
  if (wysihtml5.lang.array([
5926
5982
  "div", "pre", "p",
@@ -5949,7 +6005,7 @@ wysihtml5.dom.parse = (function() {
5949
6005
  // Converts all childnodes
5950
6006
  for (i=0; i<oldChildsLength; i++) {
5951
6007
  if (oldChilds[i]) {
5952
- newChild = _convert(oldChilds[i], cleanUp, clearInternals);
6008
+ newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
5953
6009
  if (newChild) {
5954
6010
  if (oldChilds[i] === newChild) {
5955
6011
  i--;
@@ -5982,12 +6038,31 @@ wysihtml5.dom.parse = (function() {
5982
6038
  return newNode;
5983
6039
  }
5984
6040
 
6041
+ function _applySelectorRules (element, selectorRules) {
6042
+ var sel, method, els;
6043
+
6044
+ for (sel in selectorRules) {
6045
+ if (selectorRules.hasOwnProperty(sel)) {
6046
+ if (wysihtml5.lang.object(selectorRules[sel]).isFunction()) {
6047
+ method = selectorRules[sel];
6048
+ } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) {
6049
+ method = elementHandlingMethods[selectorRules[sel]];
6050
+ }
6051
+ els = element.querySelectorAll(sel);
6052
+ for (var i = els.length; i--;) {
6053
+ method(els[i]);
6054
+ }
6055
+ }
6056
+ }
6057
+ }
6058
+
5985
6059
  function _handleElement(oldNode, clearInternals) {
5986
6060
  var rule,
5987
6061
  newNode,
5988
6062
  tagRules = currentRules.tags,
5989
6063
  nodeName = oldNode.nodeName.toLowerCase(),
5990
- scopeName = oldNode.scopeName;
6064
+ scopeName = oldNode.scopeName,
6065
+ renameTag;
5991
6066
 
5992
6067
  /**
5993
6068
  * We already parsed that element
@@ -6039,14 +6114,25 @@ wysihtml5.dom.parse = (function() {
6039
6114
  return null;
6040
6115
  }
6041
6116
 
6042
- newNode = oldNode.ownerDocument.createElement(rule.rename_tag || nodeName);
6043
- _handleAttributes(oldNode, newNode, rule, clearInternals);
6044
- _handleStyles(oldNode, newNode, rule);
6045
- // tests if type condition is met or node should be removed/unwrapped
6117
+ // tests if type condition is met or node should be removed/unwrapped/renamed
6046
6118
  if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
6047
- return (rule.remove_action && rule.remove_action == "unwrap") ? false : null;
6119
+ if (rule.remove_action) {
6120
+ if (rule.remove_action === "unwrap") {
6121
+ return false;
6122
+ } else if (rule.remove_action === "rename") {
6123
+ renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME;
6124
+ } else {
6125
+ return null;
6126
+ }
6127
+ } else {
6128
+ return null;
6129
+ }
6048
6130
  }
6049
6131
 
6132
+ newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName);
6133
+ _handleAttributes(oldNode, newNode, rule, clearInternals);
6134
+ _handleStyles(oldNode, newNode, rule);
6135
+
6050
6136
  oldNode = null;
6051
6137
 
6052
6138
  if (newNode.normalize) { newNode.normalize(); }
@@ -6134,7 +6220,7 @@ wysihtml5.dom.parse = (function() {
6134
6220
  if (definition.attrs) {
6135
6221
  for (a in definition.attrs) {
6136
6222
  if (definition.attrs.hasOwnProperty(a)) {
6137
- attr = _getAttribute(oldNode, a);
6223
+ attr = wysihtml5.dom.getAttribute(oldNode, a);
6138
6224
  if (typeof(attr) === "string") {
6139
6225
  if (attr.search(definition.attrs[a]) > -1) {
6140
6226
  return true;
@@ -6147,24 +6233,79 @@ wysihtml5.dom.parse = (function() {
6147
6233
  }
6148
6234
 
6149
6235
  function _handleStyles(oldNode, newNode, rule) {
6150
- var s;
6236
+ var s, v;
6151
6237
  if(rule && rule.keep_styles) {
6152
6238
  for (s in rule.keep_styles) {
6153
6239
  if (rule.keep_styles.hasOwnProperty(s)) {
6154
- if (s == "float") {
6240
+ v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s];
6241
+ // value can be regex and if so should match or style skipped
6242
+ if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) {
6243
+ continue;
6244
+ }
6245
+ if (s === "float") {
6155
6246
  // IE compability
6156
- if (oldNode.style.styleFloat) {
6157
- newNode.style.styleFloat = oldNode.style.styleFloat;
6158
- }
6159
- if (oldNode.style.cssFloat) {
6160
- newNode.style.cssFloat = oldNode.style.cssFloat;
6161
- }
6247
+ newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v;
6162
6248
  } else if (oldNode.style[s]) {
6163
- newNode.style[s] = oldNode.style[s];
6249
+ newNode.style[s] = v;
6164
6250
  }
6165
6251
  }
6166
6252
  }
6167
6253
  }
6254
+ };
6255
+
6256
+ function _getAttributesBeginningWith(beginning, attributes) {
6257
+ var returnAttributes = [];
6258
+ for (var attr in attributes) {
6259
+ if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) {
6260
+ returnAttributes.push(attr);
6261
+ }
6262
+ }
6263
+ return returnAttributes;
6264
+ }
6265
+
6266
+ function _checkAttribute(attributeName, attributeValue, methodName, nodeName) {
6267
+ var method = attributeCheckMethods[methodName],
6268
+ newAttributeValue;
6269
+
6270
+ if (method) {
6271
+ if (attributeValue || (attributeName === "alt" && nodeName == "IMG")) {
6272
+ newAttributeValue = method(attributeValue);
6273
+ if (typeof(newAttributeValue) === "string") {
6274
+ return newAttributeValue;
6275
+ }
6276
+ }
6277
+ }
6278
+
6279
+ return false;
6280
+ }
6281
+
6282
+ function _checkAttributes(oldNode, local_attributes) {
6283
+ var globalAttributes = wysihtml5.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes
6284
+ checkAttributes = wysihtml5.lang.object(globalAttributes).merge( wysihtml5.lang.object(local_attributes || {}).clone()).get(),
6285
+ attributes = {},
6286
+ oldAttributes = wysihtml5.dom.getAttributes(oldNode),
6287
+ attributeName, newValue, matchingAttributes;
6288
+
6289
+ for (attributeName in checkAttributes) {
6290
+ if ((/\*$/).test(attributeName)) {
6291
+
6292
+ matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes);
6293
+ for (var i = 0, imax = matchingAttributes.length; i < imax; i++) {
6294
+
6295
+ newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName);
6296
+ if (newValue !== false) {
6297
+ attributes[matchingAttributes[i]] = newValue;
6298
+ }
6299
+ }
6300
+ } else {
6301
+ newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName);
6302
+ if (newValue !== false) {
6303
+ attributes[attributeName] = newValue;
6304
+ }
6305
+ }
6306
+ }
6307
+
6308
+ return attributes;
6168
6309
  }
6169
6310
 
6170
6311
  // TODO: refactor. Too long to read
@@ -6174,7 +6315,6 @@ wysihtml5.dom.parse = (function() {
6174
6315
  addClass = rule.add_class, // add classes based on existing attributes
6175
6316
  addStyle = rule.add_style, // add styles based on existing attributes
6176
6317
  setAttributes = rule.set_attributes, // attributes to set on the current node
6177
- checkAttributes = rule.check_attributes, // check/convert values of attributes
6178
6318
  allowedClasses = currentRules.classes,
6179
6319
  i = 0,
6180
6320
  classes = [],
@@ -6186,29 +6326,14 @@ wysihtml5.dom.parse = (function() {
6186
6326
  currentClass,
6187
6327
  newClass,
6188
6328
  attributeName,
6189
- newAttributeValue,
6190
- method,
6191
- oldAttribute;
6329
+ method;
6192
6330
 
6193
6331
  if (setAttributes) {
6194
6332
  attributes = wysihtml5.lang.object(setAttributes).clone();
6195
6333
  }
6196
6334
 
6197
- if (checkAttributes) {
6198
- for (attributeName in checkAttributes) {
6199
- method = attributeCheckMethods[checkAttributes[attributeName]];
6200
- if (!method) {
6201
- continue;
6202
- }
6203
- oldAttribute = _getAttribute(oldNode, attributeName);
6204
- if (oldAttribute || (attributeName === "alt" && oldNode.nodeName == "IMG")) {
6205
- newAttributeValue = method(oldAttribute);
6206
- if (typeof(newAttributeValue) === "string") {
6207
- attributes[attributeName] = newAttributeValue;
6208
- }
6209
- }
6210
- }
6211
- }
6335
+ // check/convert values of attributes
6336
+ attributes = wysihtml5.lang.object(attributes).merge(_checkAttributes(oldNode, rule.check_attributes)).get();
6212
6337
 
6213
6338
  if (setClass) {
6214
6339
  classes.push(setClass);
@@ -6220,7 +6345,7 @@ wysihtml5.dom.parse = (function() {
6220
6345
  if (!method) {
6221
6346
  continue;
6222
6347
  }
6223
- newClass = method(_getAttribute(oldNode, attributeName));
6348
+ newClass = method(wysihtml5.dom.getAttribute(oldNode, attributeName));
6224
6349
  if (typeof(newClass) === "string") {
6225
6350
  classes.push(newClass);
6226
6351
  }
@@ -6234,7 +6359,7 @@ wysihtml5.dom.parse = (function() {
6234
6359
  continue;
6235
6360
  }
6236
6361
 
6237
- newStyle = method(_getAttribute(oldNode, attributeName));
6362
+ newStyle = method(wysihtml5.dom.getAttribute(oldNode, attributeName));
6238
6363
  if (typeof(newStyle) === "string") {
6239
6364
  styles.push(newStyle);
6240
6365
  }
@@ -6243,7 +6368,27 @@ wysihtml5.dom.parse = (function() {
6243
6368
 
6244
6369
 
6245
6370
  if (typeof(allowedClasses) === "string" && allowedClasses === "any" && oldNode.getAttribute("class")) {
6246
- attributes["class"] = oldNode.getAttribute("class");
6371
+ if (currentRules.classes_blacklist) {
6372
+ oldClasses = oldNode.getAttribute("class");
6373
+ if (oldClasses) {
6374
+ classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
6375
+ }
6376
+
6377
+ classesLength = classes.length;
6378
+ for (; i<classesLength; i++) {
6379
+ currentClass = classes[i];
6380
+ if (!currentRules.classes_blacklist[currentClass]) {
6381
+ newClasses.push(currentClass);
6382
+ }
6383
+ }
6384
+
6385
+ if (newClasses.length) {
6386
+ attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
6387
+ }
6388
+
6389
+ } else {
6390
+ attributes["class"] = oldNode.getAttribute("class");
6391
+ }
6247
6392
  } else {
6248
6393
  // make sure that wysihtml5 temp class doesn't get stripped out
6249
6394
  if (!clearInternals) {
@@ -6304,49 +6449,6 @@ wysihtml5.dom.parse = (function() {
6304
6449
  }
6305
6450
  }
6306
6451
 
6307
- /**
6308
- * IE gives wrong results for hasAttribute/getAttribute, for example:
6309
- * var td = document.createElement("td");
6310
- * td.getAttribute("rowspan"); // => "1" in IE
6311
- *
6312
- * Therefore we have to check the element's outerHTML for the attribute
6313
- */
6314
- var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
6315
- function _getAttribute(node, attributeName) {
6316
- attributeName = attributeName.toLowerCase();
6317
- var nodeName = node.nodeName;
6318
- if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
6319
- // Get 'src' attribute value via object property since this will always contain the
6320
- // full absolute url (http://...)
6321
- // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
6322
- // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
6323
- return node.src;
6324
- } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
6325
- // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
6326
- var outerHTML = node.outerHTML.toLowerCase(),
6327
- // TODO: This might not work for attributes without value: <input disabled>
6328
- hasAttribute = outerHTML.indexOf(" " + attributeName + "=") != -1;
6329
-
6330
- return hasAttribute ? node.getAttribute(attributeName) : null;
6331
- } else{
6332
- return node.getAttribute(attributeName);
6333
- }
6334
- }
6335
-
6336
- /**
6337
- * Check whether the given node is a proper loaded image
6338
- * FIXME: Returns undefined when unknown (Chrome, Safari)
6339
- */
6340
- function _isLoadedImage(node) {
6341
- try {
6342
- return node.complete && !node.mozMatchesSelector(":-moz-broken");
6343
- } catch(e) {
6344
- if (node.complete && node.readyState === "complete") {
6345
- return true;
6346
- }
6347
- }
6348
- }
6349
-
6350
6452
  var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
6351
6453
  function _handleText(oldNode) {
6352
6454
  var nextSibling = oldNode.nextSibling;
@@ -6531,8 +6633,18 @@ wysihtml5.dom.parse = (function() {
6531
6633
  })()
6532
6634
  };
6533
6635
 
6534
- return parse;
6535
- })();
6636
+ var elementHandlingMethods = {
6637
+ unwrap: function (element) {
6638
+ wysihtml5.dom.unwrap(element);
6639
+ },
6640
+
6641
+ remove: function (element) {
6642
+ element.parentNode.removeChild(element);
6643
+ }
6644
+ };
6645
+
6646
+ return parse(elementOrHtml_current, config_current);
6647
+ };
6536
6648
  ;/**
6537
6649
  * Checks for empty text node childs and removes them
6538
6650
  *
@@ -7159,7 +7271,7 @@ wysihtml5.dom.getAttribute = function(node, attributeName) {
7159
7271
  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
7160
7272
  attributeName = attributeName.toLowerCase();
7161
7273
  var nodeName = node.nodeName;
7162
- if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
7274
+ if (nodeName == "IMG" && attributeName == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
7163
7275
  // Get 'src' attribute value via object property since this will always contain the
7164
7276
  // full absolute url (http://...)
7165
7277
  // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
@@ -7176,6 +7288,52 @@ wysihtml5.dom.getAttribute = function(node, attributeName) {
7176
7288
  return node.getAttribute(attributeName);
7177
7289
  }
7178
7290
  };
7291
+ ;/**
7292
+ * Get all attributes of an element
7293
+ *
7294
+ * IE gives wrong results for hasAttribute/getAttribute, for example:
7295
+ * var td = document.createElement("td");
7296
+ * td.getAttribute("rowspan"); // => "1" in IE
7297
+ *
7298
+ * Therefore we have to check the element's outerHTML for the attribute
7299
+ */
7300
+
7301
+ wysihtml5.dom.getAttributes = function(node) {
7302
+ var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly(),
7303
+ nodeName = node.nodeName,
7304
+ attributes = [],
7305
+ attr;
7306
+
7307
+ for (attr in node.attributes) {
7308
+ if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr))) {
7309
+ if (node.attributes[attr].specified) {
7310
+ if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
7311
+ attributes['src'] = node.src;
7312
+ } else if (wysihtml5.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) {
7313
+ if (node.attributes[attr].value !== 1) {
7314
+ attributes[node.attributes[attr].name] = node.attributes[attr].value;
7315
+ }
7316
+ } else {
7317
+ attributes[node.attributes[attr].name] = node.attributes[attr].value;
7318
+ }
7319
+ }
7320
+ }
7321
+ }
7322
+ return attributes;
7323
+ };;/**
7324
+ * Check whether the given node is a proper loaded image
7325
+ * FIXME: Returns undefined when unknown (Chrome, Safari)
7326
+ */
7327
+
7328
+ wysihtml5.dom.isLoadedImage = function (node) {
7329
+ try {
7330
+ return node.complete && !node.mozMatchesSelector(":-moz-broken");
7331
+ } catch(e) {
7332
+ if (node.complete && node.readyState === "complete") {
7333
+ return true;
7334
+ }
7335
+ }
7336
+ };
7179
7337
  ;(function(wysihtml5) {
7180
7338
 
7181
7339
  var api = wysihtml5.dom;
@@ -8143,6 +8301,46 @@ wysihtml5.dom.query = function(elements, query) {
8143
8301
  }
8144
8302
  node.parentNode.removeChild(node);
8145
8303
  }
8304
+ };;/*
8305
+ * Methods for fetching pasted html before it gets inserted into content
8306
+ **/
8307
+
8308
+ /* Modern event.clipboardData driven approach.
8309
+ * Advantage is that it does not have to loose selection or modify dom to catch the data.
8310
+ * IE does not support though.
8311
+ **/
8312
+ wysihtml5.dom.getPastedHtml = function(event) {
8313
+ var html;
8314
+ if (event.clipboardData) {
8315
+ if (wysihtml5.lang.array(event.clipboardData.types).contains('text/html')) {
8316
+ html = event.clipboardData.getData('text/html');
8317
+ } else if (wysihtml5.lang.array(event.clipboardData.types).contains('text/plain')) {
8318
+ html = wysihtml5.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true);
8319
+ }
8320
+ }
8321
+ return html;
8322
+ };
8323
+
8324
+ /* Older temprorary contenteditable as paste source catcher method for fallbacks */
8325
+ wysihtml5.dom.getPastedHtmlWithDiv = function (composer, f) {
8326
+ var selBookmark = composer.selection.getBookmark(),
8327
+ doc = composer.element.ownerDocument,
8328
+ cleanerDiv = coc.createElement('DIV');
8329
+
8330
+ cleanerDiv.style.width = "1px";
8331
+ cleanerDiv.style.height = "1px";
8332
+ cleanerDiv.style.visibility = "hidden";
8333
+ cleanerDiv.style.overflow = "hidden";
8334
+
8335
+ cleanerDiv.setAttribute('contenteditable', 'true');
8336
+ doc.body.appendChild(cleanerDiv);
8337
+ cleanerDiv.focus();
8338
+
8339
+ setTimeout(function () {
8340
+ composer.selection.setBookmark(selBookmark);
8341
+ f(cleanerDiv.innerHTML);
8342
+ cleanerDiv.parentNode.removeChild(cleanerDiv);
8343
+ }, 0);
8146
8344
  };;/**
8147
8345
  * Fix most common html formatting misbehaviors of browsers implementation when inserting
8148
8346
  * content via copy & paste contentEditable
@@ -8150,52 +8348,76 @@ wysihtml5.dom.query = function(elements, query) {
8150
8348
  * @author Christopher Blum
8151
8349
  */
8152
8350
  wysihtml5.quirks.cleanPastedHTML = (function() {
8153
- // TODO: We probably need more rules here
8154
- var defaultRules = {
8155
- // When pasting underlined links <a> into a contentEditable, IE thinks, it has to insert <u> to keep the styling
8156
- "a u": wysihtml5.dom.replaceWithChildNodes
8351
+
8352
+ var styleToRegex = function (styleStr) {
8353
+ var trimmedStr = wysihtml5.lang.string(styleStr).trim(),
8354
+ escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
8355
+
8356
+ return new RegExp("^((?!^" + escapedStr + "$).)*$", "i");
8157
8357
  };
8158
8358
 
8159
- function cleanPastedHTML(elementOrHtml, rules, context) {
8160
- rules = rules || defaultRules;
8161
- context = context || elementOrHtml.ownerDocument || document;
8162
-
8163
- var element,
8164
- isString = typeof(elementOrHtml) === "string",
8165
- method,
8166
- matches,
8167
- matchesLength,
8168
- i,
8169
- j = 0, n;
8170
- if (isString) {
8171
- element = wysihtml5.dom.getAsDom(elementOrHtml, context);
8172
- } else {
8173
- element = elementOrHtml;
8174
- }
8359
+ var extendRulesWithStyleExceptions = function (rules, exceptStyles) {
8360
+ var newRules = wysihtml5.lang.object(rules).clone(true),
8361
+ tag, style;
8362
+
8363
+ for (tag in newRules.tags) {
8175
8364
 
8176
- for (i in rules) {
8177
- matches = element.querySelectorAll(i);
8178
- method = rules[i];
8179
- matchesLength = matches.length;
8180
- for (; j<matchesLength; j++) {
8181
- method(matches[j]);
8365
+ if (newRules.tags.hasOwnProperty(tag)) {
8366
+ if (newRules.tags[tag].keep_styles) {
8367
+ for (style in newRules.tags[tag].keep_styles) {
8368
+ if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) {
8369
+ if (exceptStyles[style]) {
8370
+ newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]);
8371
+ }
8372
+ }
8373
+ }
8374
+ }
8182
8375
  }
8183
8376
  }
8184
8377
 
8185
- // replace joined non-breakable spaces with unjoined
8186
- var txtnodes = wysihtml5.dom.getTextNodes(element);
8187
- for (n = txtnodes.length; n--;) {
8188
- txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
8378
+ return newRules;
8379
+ };
8380
+
8381
+ var pickRuleset = function(ruleset, html) {
8382
+ var pickedSet, defaultSet;
8383
+
8384
+ if (!ruleset) {
8385
+ return null;
8189
8386
  }
8190
8387
 
8191
- matches = elementOrHtml = rules = null;
8388
+ for (var i = 0, max = ruleset.length; i < max; i++) {
8389
+ if (!ruleset[i].condition) {
8390
+ defaultSet = ruleset[i].set;
8391
+ }
8392
+ if (ruleset[i].condition && ruleset[i].condition.test(html)) {
8393
+ return ruleset[i].set;
8394
+ }
8395
+ }
8192
8396
 
8193
- return isString ? element.innerHTML : element;
8194
- }
8397
+ return defaultSet;
8398
+ };
8195
8399
 
8196
- return cleanPastedHTML;
8197
- })();
8198
- ;/**
8400
+ return function(html, options) {
8401
+ var exceptStyles = {
8402
+ 'color': wysihtml5.dom.getStyle("color").from(options.referenceNode),
8403
+ 'fontSize': wysihtml5.dom.getStyle("font-size").from(options.referenceNode)
8404
+ },
8405
+ rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles),
8406
+ newHtml;
8407
+
8408
+ newHtml = wysihtml5.dom.parse(html, {
8409
+ "rules": rules,
8410
+ "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content
8411
+ "context": options.referenceNode.ownerDocument,
8412
+ "uneditableClass": options.uneditableClass,
8413
+ "clearInternals" : true, // don't paste temprorary selection and other markings
8414
+ "unjoinNbsps" : true
8415
+ });
8416
+
8417
+ return newHtml;
8418
+ };
8419
+
8420
+ })();;/**
8199
8421
  * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
8200
8422
  *
8201
8423
  * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
@@ -8825,7 +9047,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8825
9047
  return false;
8826
9048
  },
8827
9049
 
8828
- // TODO: Figure out a method from following 3 that would work universally
9050
+ // TODO: Figure out a method from following 2 that would work universally
8829
9051
  executeAndRestoreRangy: function(method, restoreScrollPosition) {
8830
9052
  var win = this.doc.defaultView || this.doc.parentWindow,
8831
9053
  sel = rangy.saveSelection(win);
@@ -8938,10 +9160,18 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8938
9160
  */
8939
9161
  insertHTML: function(html) {
8940
9162
  var range = rangy.createRange(this.doc),
8941
- node = range.createContextualFragment(html),
8942
- lastChild = node.lastChild;
9163
+ node = this.doc.createElement('DIV'),
9164
+ fragment = this.doc.createDocumentFragment(),
9165
+ lastChild;
9166
+
9167
+ node.innerHTML = html;
9168
+ lastChild = node.lastChild;
9169
+
9170
+ while (node.firstChild) {
9171
+ fragment.appendChild(node.firstChild);
9172
+ }
9173
+ this.insertNode(fragment);
8943
9174
 
8944
- this.insertNode(node);
8945
9175
  if (lastChild) {
8946
9176
  this.setAfter(lastChild);
8947
9177
  }
@@ -12560,7 +12790,7 @@ wysihtml5.views.View = Base.extend(
12560
12790
  container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
12561
12791
  element = this.element,
12562
12792
  focusBlurElement = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? element : this.sandbox.getWindow(),
12563
- pasteEvents = ["drop", "paste"],
12793
+ pasteEvents = ["drop", "paste", "beforepaste"],
12564
12794
  interactionEvents = ["drop", "paste", "mouseup", "focus", "keyup"];
12565
12795
 
12566
12796
  // --------- destroy:composer event ---------
@@ -12633,9 +12863,9 @@ wysihtml5.views.View = Base.extend(
12633
12863
  });
12634
12864
 
12635
12865
  dom.observe(element, pasteEvents, function(event) {
12636
- setTimeout(function() {
12866
+ //setTimeout(function() {
12637
12867
  that.parent.fire(event.type, event).fire(event.type + ":composer", event);
12638
- }, 0);
12868
+ //}, 0);
12639
12869
  });
12640
12870
 
12641
12871
  // --------- neword event ---------
@@ -12993,10 +13223,12 @@ wysihtml5.views.View = Base.extend(
12993
13223
  handleTables: true,
12994
13224
  // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
12995
13225
  handleTabKey: true,
12996
- // Object which includes parser rules to apply when html gets inserted via copy & paste
13226
+ // Object which includes parser rules to apply when html gets cleaned
12997
13227
  // See parser_rules/*.js for examples
12998
13228
  parserRules: { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
12999
- // Parser method to use when the user inserts content via copy & paste
13229
+ // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
13230
+ pasteParserRulesets: null,
13231
+ // Parser method to use when the user inserts content
13000
13232
  parser: wysihtml5.dom.parse,
13001
13233
  // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
13002
13234
  composerClassName: "wysihtml5-editor",
@@ -13141,14 +13373,40 @@ wysihtml5.views.View = Base.extend(
13141
13373
  * - Observes for paste and drop
13142
13374
  */
13143
13375
  _initParser: function() {
13144
- this.on("paste:composer", function() {
13145
- var keepScrollPosition = true,
13146
- that = this;
13147
- that.composer.selection.executeAndRestore(function() {
13148
- wysihtml5.quirks.cleanPastedHTML(that.composer.element);
13149
- that.parse(that.composer.element);
13150
- }, keepScrollPosition);
13376
+ var that = this,
13377
+ oldHtml,
13378
+ cleanHtml;
13379
+
13380
+ if (wysihtml5.browser.supportsModenPaste()) {
13381
+ this.on("paste:composer", function(event) {
13382
+ event.preventDefault();
13383
+ oldHtml = wysihtml5.dom.getPastedHtml(event);
13384
+ if (oldHtml) {
13385
+ that._cleanAndPaste(oldHtml);
13386
+ }
13387
+ });
13388
+
13389
+ } else {
13390
+ this.on("beforepaste:composer", function(event) {
13391
+ event.preventDefault();
13392
+ wysihtml5.dom.getPastedHtmlWithDiv(that.composer, function(pastedHTML) {
13393
+ if (pastedHTML) {
13394
+ that._cleanAndPaste(pastedHTML);
13395
+ }
13396
+ });
13397
+ });
13398
+
13399
+ }
13400
+ },
13401
+
13402
+ _cleanAndPaste: function (oldHtml) {
13403
+ var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, {
13404
+ "referenceNode": this.composer.element,
13405
+ "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
13406
+ "uneditableClass": this.config.uneditableContainerClassname
13151
13407
  });
13408
+ this.composer.selection.deleteContents();
13409
+ this.composer.selection.insertHTML(cleanHtml);
13152
13410
  }
13153
13411
  });
13154
13412
  })(wysihtml5);
@@ -13478,6 +13736,19 @@ wysihtml5.views.View = Base.extend(
13478
13736
  this._observe();
13479
13737
  if (showOnInit) { this.show(); }
13480
13738
 
13739
+ if (editor.config.classNameCommandDisabled != null) {
13740
+ CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled;
13741
+ }
13742
+ if (editor.config.classNameCommandsDisabled != null) {
13743
+ CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled;
13744
+ }
13745
+ if (editor.config.classNameCommandActive != null) {
13746
+ CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive;
13747
+ }
13748
+ if (editor.config.classNameActionActive != null) {
13749
+ CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive;
13750
+ }
13751
+
13481
13752
  var speechInputLinks = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
13482
13753
  length = speechInputLinks.length,
13483
13754
  i = 0;