WIP: Note editing, markdown to html
[oweals/karmaworld.git] / karmaworld / apps / wysihtml5 / static / wysihtml5 / wysihtml-0.4.17 / src / commands / formatBlock.js
1 (function(wysihtml5) {
2   var dom                     = wysihtml5.dom,
3       // Following elements are grouped
4       // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
5       // instead of creating a H4 within a H1 which would result in semantically invalid html
6       BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE", "DIV"];
7
8   /**
9    * Remove similiar classes (based on classRegExp)
10    * and add the desired class name
11    */
12   function _addClass(element, className, classRegExp) {
13     if (element.className) {
14       _removeClass(element, classRegExp);
15       element.className = wysihtml5.lang.string(element.className + " " + className).trim();
16     } else {
17       element.className = className;
18     }
19   }
20
21   function _addStyle(element, cssStyle, styleRegExp) {
22     _removeStyle(element, styleRegExp);
23     if (element.getAttribute('style')) {
24       element.setAttribute('style', wysihtml5.lang.string(element.getAttribute('style') + " " + cssStyle).trim());
25     } else {
26       element.setAttribute('style', cssStyle);
27     }
28   }
29
30   function _removeClass(element, classRegExp) {
31     var ret = classRegExp.test(element.className);
32     element.className = element.className.replace(classRegExp, "");
33     if (wysihtml5.lang.string(element.className).trim() == '') {
34         element.removeAttribute('class');
35     }
36     return ret;
37   }
38
39   function _removeStyle(element, styleRegExp) {
40     var ret = styleRegExp.test(element.getAttribute('style'));
41     element.setAttribute('style', (element.getAttribute('style') || "").replace(styleRegExp, ""));
42     if (wysihtml5.lang.string(element.getAttribute('style') || "").trim() == '') {
43       element.removeAttribute('style');
44     }
45     return ret;
46   }
47
48   function _removeLastChildIfLineBreak(node) {
49     var lastChild = node.lastChild;
50     if (lastChild && _isLineBreak(lastChild)) {
51       lastChild.parentNode.removeChild(lastChild);
52     }
53   }
54
55   function _isLineBreak(node) {
56     return node.nodeName === "BR";
57   }
58
59   /**
60    * Execute native query command
61    * and if necessary modify the inserted node's className
62    */
63   function _execCommand(doc, composer, command, nodeName, className) {
64     var ranges = composer.selection.getOwnRanges();
65     for (var i = ranges.length; i--;){
66       composer.selection.getSelection().removeAllRanges();
67       composer.selection.setSelection(ranges[i]);
68       if (className) {
69         var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) {
70           var target = event.target,
71               displayStyle;
72           if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
73             return;
74           }
75           displayStyle = dom.getStyle("display").from(target);
76           if (displayStyle.substr(0, 6) !== "inline") {
77             // Make sure that only block elements receive the given class
78             target.className += " " + className;
79           }
80         });
81       }
82       doc.execCommand(command, false, nodeName);
83
84       if (eventListener) {
85         eventListener.stop();
86       }
87     }
88   }
89
90   function _selectionWrap(composer, options) {
91     if (composer.selection.isCollapsed()) {
92         composer.selection.selectLine();
93     }
94
95     var surroundedNodes = composer.selection.surround(options);
96     for (var i = 0, imax = surroundedNodes.length; i < imax; i++) {
97       wysihtml5.dom.lineBreaks(surroundedNodes[i]).remove();
98       _removeLastChildIfLineBreak(surroundedNodes[i]);
99     }
100
101     // rethink restoring selection
102     // composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
103   }
104
105   function _hasClasses(element) {
106     return !!wysihtml5.lang.string(element.className).trim();
107   }
108
109   function _hasStyles(element) {
110     return !!wysihtml5.lang.string(element.getAttribute('style') || '').trim();
111   }
112
113   wysihtml5.commands.formatBlock = {
114     exec: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
115       var doc             = composer.doc,
116           blockElements    = this.state(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp),
117           useLineBreaks   = composer.config.useLineBreaks,
118           defaultNodeName = useLineBreaks ? "DIV" : "P",
119           selectedNodes, classRemoveAction, blockRenameFound, styleRemoveAction, blockElement;
120       nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
121
122       if (blockElements.length) {
123         composer.selection.executeAndRestoreRangy(function() {
124           for (var b = blockElements.length; b--;) {
125             if (classRegExp) {
126               classRemoveAction = _removeClass(blockElements[b], classRegExp);
127             }
128             if (styleRegExp) {
129               styleRemoveAction = _removeStyle(blockElements[b], styleRegExp);
130             }
131
132             if ((styleRemoveAction || classRemoveAction) && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
133               // dont rename or remove element when just setting block formating class or style
134               return;
135             }
136
137             var hasClasses = _hasClasses(blockElements[b]),
138                 hasStyles = _hasStyles(blockElements[b]);
139
140             if (!hasClasses && !hasStyles && (useLineBreaks || nodeName === "P")) {
141               // Insert a line break afterwards and beforewards when there are siblings
142               // that are not of type line break or block element
143               wysihtml5.dom.lineBreaks(blockElements[b]).add();
144               dom.replaceWithChildNodes(blockElements[b]);
145             } else {
146               // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
147               dom.renameElement(blockElements[b], nodeName === "P" ? "DIV" : defaultNodeName);
148             }
149           }
150         });
151
152         return;
153       }
154
155       // Find similiar block element and rename it (<h2 class="foo"></h2>  =>  <h1 class="foo"></h1>)
156       if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
157         selectedNodes = composer.selection.findNodesInSelection(BLOCK_ELEMENTS_GROUP).concat(composer.selection.getSelectedOwnNodes());
158         composer.selection.executeAndRestoreRangy(function() {
159           for (var n = selectedNodes.length; n--;) {
160             blockElement = dom.getParentElement(selectedNodes[n], {
161               nodeName: BLOCK_ELEMENTS_GROUP
162             });
163             if (blockElement == composer.element) {
164               blockElement = null;
165             }
166             if (blockElement) {
167                 // Rename current block element to new block element and add class
168                 if (nodeName) {
169                   blockElement = dom.renameElement(blockElement, nodeName);
170                 }
171                 if (className) {
172                   _addClass(blockElement, className, classRegExp);
173                 }
174                 if (cssStyle) {
175                   _addStyle(blockElement, cssStyle, styleRegExp);
176                 }
177               blockRenameFound = true;
178             }
179           }
180
181         });
182
183         if (blockRenameFound) {
184           return;
185         }
186       }
187
188       _selectionWrap(composer, {
189         "nodeName": (nodeName || defaultNodeName),
190         "className": className || null,
191         "cssStyle": cssStyle || null
192       });
193     },
194
195     state: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
196       var nodes = composer.selection.getSelectedOwnNodes(),
197           parents = [],
198           parent;
199
200       nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
201
202       //var selectedNode = composer.selection.getSelectedNode();
203       for (var i = 0, maxi = nodes.length; i < maxi; i++) {
204         parent = dom.getParentElement(nodes[i], {
205           nodeName:     nodeName,
206           className:    className,
207           classRegExp:  classRegExp,
208           cssStyle:     cssStyle,
209           styleRegExp:  styleRegExp
210         });
211         if (parent && wysihtml5.lang.array(parents).indexOf(parent) == -1) {
212           parents.push(parent);
213         }
214       }
215       if (parents.length == 0) {
216         return false;
217       }
218       return parents;
219     }
220
221
222   };
223 })(wysihtml5);