wysihtml5x-rails 0.4.13 → 0.4.14

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.
@@ -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;