//>>built define("dojox/editor/plugins/NormalizeStyle", [ "dojo", "dijit", "dojox", "dijit/_editor/html", "dijit/_editor/_Plugin", "dojo/_base/connect", "dojo/_base/declare" ], function(dojo, dijit, dojox) { dojo.declare("dojox.editor.plugins.NormalizeStyle",dijit._editor._Plugin,{ // summary: // This plugin provides NormalizeStyle cabability to the editor. It is // a headless plugin that tries to normalize how content is styled when // it comes out of th editor ('b' or css). It also auto-converts // incoming content to the proper one expected by the browser as well so // that the native styling buttons work. // mode [public] String // A String variable indicating if it should use semantic tags 'b', 'i', etc, or // CSS styling. The default is semantic. mode: "semantic", // condenseSpans [public] Boolean // A boolean variable indicating if it should try to condense // 'span''span''span' styles when in css mode // The default is true, it will try to combine where it can. condenseSpans: true, setEditor: function(editor){ // summary: // Over-ride for the setting of the editor. // editor: Object // The editor to configure for this plugin to use. this.editor = editor; editor.customUndo = true; if(this.mode === "semantic"){ this.editor.contentDomPostFilters.push(dojo.hitch(this, this._convertToSemantic)); }else if(this.mode === "css"){ this.editor.contentDomPostFilters.push(dojo.hitch(this, this._convertToCss)); } // Pre DOM filters are usually based on what browser, as they all use different ways to // apply styles with actions and modify them. if(dojo.isIE){ // IE still uses semantic tags most of the time, so convert to that. this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToSemantic)); this._browserFilter = this._convertToSemantic; }else if(dojo.isWebKit){ this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToCss)); this._browserFilter = this._convertToCss; }else if(dojo.isMoz){ //Editor currently forces Moz into semantic mode, so we need to match. Ideally //editor could get rid of that and just use CSS mode, which would work cleaner //That's why this is split out, to make it easy to change later. this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToSemantic)); this._browserFilter = this._convertToSemantic; }else{ this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToSemantic)); this._browserFilter = this._convertToSemantic; } // Set up the inserthtml impl over-ride. This catches semi-paste events and // tries to normalize them too. if(this.editor._inserthtmlImpl){ this.editor._oldInsertHtmlImpl = this.editor._inserthtmlImpl; } this.editor._inserthtmlImpl = dojo.hitch(this, this._inserthtmlImpl); }, _convertToSemantic: function(node){ // summary: // A function to convert the HTML structure of 'node' into // semantic tags where possible. // node: DOMNode // The node to process. // tags: // private if(node){ var w = this.editor.window; var self = this; var convertNode = function(cNode){ if(cNode.nodeType == 1){ if(cNode.id !== "dijitEditorBody"){ var style = cNode.style; var tag = cNode.tagName?cNode.tagName.toLowerCase():""; var sTag; if(style && tag != "table" && tag != "ul" && tag != "ol"){ // Avoid wrapper blocks that have specific underlying structure, as injecting // spans/etc there is invalid. // Lets check and convert certain node/style types. var fw = style.fontWeight? style.fontWeight.toLowerCase() : ""; var fs = style.fontStyle? style.fontStyle.toLowerCase() : ""; var td = style.textDecoration? style.textDecoration.toLowerCase() : ""; var s = style.fontSize?style.fontSize.toLowerCase() : ""; var bc = style.backgroundColor?style.backgroundColor.toLowerCase() : ""; var c = style.color?style.color.toLowerCase() : ""; var wrapNodes = function(wrap, pNode){ if(wrap){ while(pNode.firstChild){ wrap.appendChild(pNode.firstChild); } if(tag == "span" && !pNode.style.cssText){ // A styler tag with nothing extra in it, so lets remove it. dojo.place(wrap, pNode, "before"); pNode.parentNode.removeChild(pNode); pNode = wrap; }else{ pNode.appendChild(wrap); } } return pNode; }; switch(fw){ case "bold": case "bolder": case "700": case "800": case "900": sTag = dojo.withGlobal(w, "create", dojo, ["b", {}] ); cNode.style.fontWeight = ""; break; } cNode = wrapNodes(sTag, cNode); sTag = null; if(fs == "italic"){ sTag = dojo.withGlobal(w, "create", dojo, ["i", {}] ); cNode.style.fontStyle = ""; } cNode = wrapNodes(sTag, cNode); sTag = null; if(td){ var da = td.split(" "); var count = 0; dojo.forEach(da, function(s){ switch(s){ case "underline": sTag = dojo.withGlobal(w, "create", dojo, ["u", {}] ); break; case "line-through": sTag = dojo.withGlobal(w, "create", dojo, ["strike", {}] ); break; } count++; if(count == da.length){ // Last one, clear the decor and see if we can span strip on wrap. cNode.style.textDecoration = ""; } cNode = wrapNodes(sTag, cNode); sTag = null; }); } if(s){ var sizeMap = { "xx-small": 1, "x-small": 2, "small": 3, "medium": 4, "large": 5, "x-large": 6, "xx-large": 7, "-webkit-xxx-large": 7 }; // Convert point or px size to size // to something roughly mappable. if(s.indexOf("pt") > 0){ s = s.substring(0,s.indexOf("pt")); s = parseInt(s); if(s < 5){ s = "xx-small"; }else if(s < 10){ s = "x-small"; }else if(s < 15){ s = "small"; }else if(s < 20){ s = "medium"; }else if(s < 25){ s = "large"; }else if(s < 30){ s = "x-large"; }else if(s > 30){ s = "xx-large"; } }else if(s.indexOf("px") > 0){ s = s.substring(0,s.indexOf("px")); s = parseInt(s); if(s < 5){ s = "xx-small"; }else if(s < 10){ s = "x-small"; }else if(s < 15){ s = "small"; }else if(s < 20){ s = "medium"; }else if(s < 25){ s = "large"; }else if(s < 30){ s = "x-large"; }else if(s > 30){ s = "xx-large"; } } var size = sizeMap[s]; if(!size){ size = 3; } sTag = dojo.withGlobal(w, "create", dojo, ["font", {size: size}] ); cNode.style.fontSize = ""; } cNode = wrapNodes(sTag, cNode); sTag = null; if(bc && tag !== "font" && self._isInline(tag)){ // IE doesn't like non-font background color crud. // Also, don't move it in if the background color is set on a block style node, // as it won't color properly once put on inline font. bc = new dojo.Color(bc).toHex(); sTag = dojo.withGlobal(w, "create", dojo, ["font", {style: {backgroundColor: bc}}] ); cNode.style.backgroundColor = ""; } if(c && tag !== "font"){ // IE doesn't like non-font background color crud. c = new dojo.Color(c).toHex(); sTag = dojo.withGlobal(w, "create", dojo, ["font", {color: c}] ); cNode.style.color = ""; } cNode = wrapNodes(sTag, cNode); sTag = null; } } if(cNode.childNodes){ // Clone it, since we may alter its position var nodes = []; dojo.forEach(cNode.childNodes, function(n){ nodes.push(n);}); dojo.forEach(nodes, convertNode); } } return cNode; }; return this._normalizeTags(convertNode(node)); } return node; }, _normalizeTags: function(node){ // summary: // A function to handle normalizing certain tag types contained under 'node' // node: // The node to search from. // tags: // Protected. var w = this.editor.window; var nodes = dojo.withGlobal(w, function() { return dojo.query("em,s,strong", node); }); if(nodes && nodes.length){ dojo.forEach(nodes, function(n){ if(n){ var tag = n.tagName?n.tagName.toLowerCase():""; var tTag; switch(tag){ case "s": tTag = "strike"; break; case "em": tTag = "i"; break; case "strong": tTag = "b"; break; } if(tTag){ var nNode = dojo.withGlobal(w, "create", dojo, [tTag, null, n, "before"] ); while(n.firstChild){ nNode.appendChild(n.firstChild); } n.parentNode.removeChild(n); } } }); } return node; }, _convertToCss: function(node){ // summary: // A function to convert the HTML structure of 'node' into // css span styles around text instead of semantic tags. // Note: It does not do compression of spans together. // node: DOMNode // The node to process // tags: // private if(node){ var w = this.editor.window; var convertNode = function(cNode) { if(cNode.nodeType == 1){ if(cNode.id !== "dijitEditorBody"){ var tag = cNode.tagName?cNode.tagName.toLowerCase():""; if(tag){ var span; switch(tag){ case "b": case "strong": // Mainly IE span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"fontWeight": "bold"}}] ); break; case "i": case "em": // Mainly IE span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"fontStyle": "italic"}}] ); break; case "u": span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"textDecoration": "underline"}}] ); break; case "strike": case "s": // Mainly WebKit. span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"textDecoration": "line-through"}}] ); break; case "font": // Try to deal with colors var styles = {}; if(dojo.attr(cNode, "color")){ styles.color = dojo.attr(cNode, "color"); } if(dojo.attr(cNode, "face")){ styles.fontFace = dojo.attr(cNode, "face"); } if(cNode.style && cNode.style.backgroundColor){ styles.backgroundColor = cNode.style.backgroundColor; } if(cNode.style && cNode.style.color){ styles.color = cNode.style.color; } var sizeMap = { 1: "xx-small", 2: "x-small", 3: "small", 4: "medium", 5: "large", 6: "x-large", 7: "xx-large" }; if(dojo.attr(cNode, "size")){ styles.fontSize = sizeMap[dojo.attr(cNode, "size")]; } span = dojo.withGlobal(w, "create", dojo, ["span", {style: styles}] ); break; } if(span){ while(cNode.firstChild){ span.appendChild(cNode.firstChild); } dojo.place(span, cNode, "before"); cNode.parentNode.removeChild(cNode); cNode = span; } } } if(cNode.childNodes){ // Clone it, since we may alter its position var nodes = []; dojo.forEach(cNode.childNodes, function(n){ nodes.push(n);}); dojo.forEach(nodes, convertNode); } } return cNode; }; node = convertNode(node); if(this.condenseSpans){ this._condenseSpans(node); } } return node; }, _condenseSpans: function(node){ // summary: // Method to condense spans if you end up with multi-wrapping from // from converting b, i, u, to span nodes. // node: // The node (and its children), to process. // tags: // private var compressSpans = function(node){ // Okay, span with no class or id and it has styles. // So, merge the styles, then collapse. Merge requires determining // all the common/different styles and anything that overlaps the style, // but a different value can't be merged. var genStyleMap = function(styleText){ var m; if(styleText){ m = {}; var styles = styleText.toLowerCase().split(";"); dojo.forEach(styles, function(s){ if(s){ var ss = s.split(":"); var key = ss[0] ? dojo.trim(ss[0]): ""; var val = ss[1] ? dojo.trim(ss[1]): ""; if(key && val){ var i; var nKey = ""; for(i = 0; i < key.length; i++){ var ch = key.charAt(i); if(ch == "-"){ i++; ch = key.charAt(i); nKey += ch.toUpperCase(); }else{ nKey += ch; } } m[nKey] = val; } } }); } return m; }; if(node && node.nodeType == 1){ var tag = node.tagName? node.tagName.toLowerCase() : ""; if(tag === "span" && node.childNodes && node.childNodes.length === 1){ // Okay, a possibly compressible span var c = node.firstChild; while(c && c.nodeType == 1 && c.tagName && c.tagName.toLowerCase() == "span"){ if(!dojo.attr(c, "class") && !dojo.attr(c, "id") && c.style){ var s1 = genStyleMap(node.style.cssText); var s2 = genStyleMap(c.style.cssText); if(s1 && s2){ // Maps, so lets see if we can combine them. var combinedMap = {}; var i; for(i in s1){ if(!s1[i] || !s2[i] || s1[i] == s2[i]){ combinedMap[i] = s1[i]; delete s2[i]; }else if(s1[i] != s2[i]){ // Collision, cannot merge. // IE does not handle combined underline strikethrough text // decorations on a single span. if(i == "textDecoration"){ combinedMap[i] = s1[i] + " " + s2[i]; delete s2[i]; }else{ combinedMap = null; } break; }else{ combinedMap = null; break; } } if(combinedMap){ for(i in s2){ combinedMap[i] = s2[i]; } dojo.style(node, combinedMap); while(c.firstChild){ node.appendChild(c.firstChild); } var t = c.nextSibling; c.parentNode.removeChild(c); c = t; }else{ c = c.nextSibling; } }else{ c = c.nextSibling; } }else{ c = c.nextSibling; } } } } if(node.childNodes && node.childNodes.length){ dojo.forEach(node.childNodes, compressSpans); } }; compressSpans(node); }, _isInline: function(tag){ // summary: // Function to determine if the current tag is an inline // element that does formatting, as we don't want to // try to combine inlines with divs on styles. // tag: // The tag to examine // tags: // private switch(tag){ case "a": case "b": case "strong": case "s": case "strike": case "i": case "u": case "em": case "sup": case "sub": case "span": case "font": case "big": case "cite": case "q": case "img": case "small": return true; default: return false; } }, _inserthtmlImpl: function(html){ // summary: // Function to trap and over-ride the editor inserthtml implementation // to try and filter it to match the editor's internal styling mode. // Helpful for plugins like PasteFromWord, in that it extra-filters // and normalizes the input if it can. // html: // The HTML string to insert. // tags: // private if(html){ var div = this.editor.document.createElement("div"); div.innerHTML = html; div = this._browserFilter(div); html = dijit._editor.getChildrenHtml(div); div.innerHTML = ""; // Call the over-ride, or if not available, just execute it. if(this.editor._oldInsertHtmlImpl){ return this.editor._oldInsertHtmlImpl(html); }else{ return this.editor.execCommand("inserthtml", html); } } return false; } }); // Register this plugin. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){ if(o.plugin){ return; } var name = o.args.name.toLowerCase(); if(name === "normalizestyle"){ o.plugin = new dojox.editor.plugins.NormalizeStyle({ mode: ("mode" in o.args)?o.args.mode:"semantic", condenseSpans: ("condenseSpans" in o.args)?o.args.condenseSpans:true }); } }); return dojox.editor.plugins.NormalizeStyle; });