WIP: Note editing, markdown to html
[oweals/karmaworld.git] / karmaworld / apps / wysihtml5 / static / wysihtml5 / wysihtml-0.4.17 / src / commands / formatInline.js
1 /**
2  * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
3  *
4  *   #1 caret in unformatted text:
5  *      abcdefg|
6  *   output:
7  *      abcdefg<b>|</b>
8  *
9  *   #2 unformatted text selected:
10  *      abc|deg|h
11  *   output:
12  *      abc<b>|deg|</b>h
13  *
14  *   #3 unformatted text selected across boundaries:
15  *      ab|c <span>defg|h</span>
16  *   output:
17  *      ab<b>|c </b><span><b>defg</b>|h</span>
18  *
19  *   #4 formatted text entirely selected
20  *      <b>|abc|</b>
21  *   output:
22  *      |abc|
23  *
24  *   #5 formatted text partially selected
25  *      <b>ab|c|</b>
26  *   output:
27  *      <b>ab</b>|c|
28  *
29  *   #6 formatted text selected across boundaries
30  *      <span>ab|c</span> <b>de|fgh</b>
31  *   output:
32  *      <span>ab|c</span> de|<b>fgh</b>
33  */
34 (function(wysihtml5) {
35   var // Treat <b> as <strong> and vice versa
36       ALIAS_MAPPING = {
37         "strong": "b",
38         "em":     "i",
39         "b":      "strong",
40         "i":      "em"
41       },
42       htmlApplier = {};
43
44   function _getTagNames(tagName) {
45     var alias = ALIAS_MAPPING[tagName];
46     return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
47   }
48
49   function _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, container) {
50     var identifier = tagName;
51     
52     if (className) {
53       identifier += ":" + className;
54     }
55     if (cssStyle) {
56       identifier += ":" + cssStyle;
57     }
58
59     if (!htmlApplier[identifier]) {
60       htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp, container);
61     }
62
63     return htmlApplier[identifier];
64   }
65
66   wysihtml5.commands.formatInline = {
67     exec: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, dontRestoreSelect, noCleanup) {
68       var range = composer.selection.createRange(),
69           ownRanges = composer.selection.getOwnRanges();
70
71       if (!ownRanges || ownRanges.length == 0) {
72         return false;
73       }
74       composer.selection.getSelection().removeAllRanges();
75
76       _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).toggleRange(ownRanges);
77
78       if (!dontRestoreSelect) {
79         range.setStart(ownRanges[0].startContainer,  ownRanges[0].startOffset);
80         range.setEnd(
81           ownRanges[ownRanges.length - 1].endContainer,
82           ownRanges[ownRanges.length - 1].endOffset
83         );
84         composer.selection.setSelection(range);
85         composer.selection.executeAndRestore(function() {
86           if (!noCleanup) {
87             composer.cleanUp();
88           }
89         }, true, true);
90       } else if (!noCleanup) {
91         composer.cleanUp();
92       }
93     },
94
95     // Executes so that if collapsed caret is in a state and executing that state it should unformat that state
96     // It is achieved by selecting the entire state element before executing.
97     // This works on built in contenteditable inline format commands
98     execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
99       var that = this;
100
101       if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
102         composer.selection.isCollapsed() &&
103         !composer.selection.caretIsLastInSelection() &&
104         !composer.selection.caretIsFirstInSelection()
105       ) {
106         var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
107         composer.selection.executeAndRestoreRangy(function() {
108           var parent = state_element.parentNode;
109           composer.selection.selectNode(state_element, true);
110           wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
111         });
112       } else {
113         if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
114           composer.selection.executeAndRestoreRangy(function() {
115             wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
116           });
117         } else {
118           wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
119         }
120       }
121     },
122
123     state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
124       var doc           = composer.doc,
125           aliasTagName  = ALIAS_MAPPING[tagName] || tagName,
126           ownRanges, isApplied;
127
128       // Check whether the document contains a node with the desired tagName
129       if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
130           !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
131         return false;
132       }
133
134        // Check whether the document contains a node with the desired className
135       if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
136          return false;
137       }
138
139       ownRanges = composer.selection.getOwnRanges();
140
141       if (!ownRanges || ownRanges.length === 0) {
142         return false;
143       }
144
145       isApplied = _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).isAppliedToRange(ownRanges);
146
147       return (isApplied && isApplied.elements) ? isApplied.elements : false;
148     }
149   };
150 })(wysihtml5);