123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560 |
- /*
- Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- if(!dojo._hasResource["dojox.editor.plugins.NormalizeStyle"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["dojox.editor.plugins.NormalizeStyle"] = true;
- dojo.provide("dojox.editor.plugins.NormalizeStyle");
- dojo.require("dijit._editor.html");
- dojo.require("dijit._editor._Plugin");
- 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
- });
- }
- });
- }
|