WIP: Note editing, markdown to html
[oweals/karmaworld.git] / karmaworld / apps / wysihtml5 / static / wysihtml5 / wysihtml-0.4.17 / src / toolbar / toolbar.js
1 /**
2  * Toolbar
3  *
4  * @param {Object} parent Reference to instance of Editor instance
5  * @param {Element} container Reference to the toolbar container element
6  *
7  * @example
8  *    <div id="toolbar">
9  *      <a data-wysihtml5-command="createLink">insert link</a>
10  *      <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a>
11  *    </div>
12  *
13  *    <script>
14  *      var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar"));
15  *    </script>
16  */
17 (function(wysihtml5) {
18   var CLASS_NAME_COMMAND_DISABLED   = "wysihtml5-command-disabled",
19       CLASS_NAME_COMMANDS_DISABLED  = "wysihtml5-commands-disabled",
20       CLASS_NAME_COMMAND_ACTIVE     = "wysihtml5-command-active",
21       CLASS_NAME_ACTION_ACTIVE      = "wysihtml5-action-active",
22       dom                           = wysihtml5.dom;
23
24   wysihtml5.toolbar.Toolbar = Base.extend(
25     /** @scope wysihtml5.toolbar.Toolbar.prototype */ {
26     constructor: function(editor, container, showOnInit) {
27       this.editor     = editor;
28       this.container  = typeof(container) === "string" ? document.getElementById(container) : container;
29       this.composer   = editor.composer;
30
31       this._getLinks("command");
32       this._getLinks("action");
33
34       this._observe();
35       if (showOnInit) { this.show(); }
36
37       if (editor.config.classNameCommandDisabled != null) {
38         CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled;
39       }
40       if (editor.config.classNameCommandsDisabled != null) {
41         CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled;
42       }
43       if (editor.config.classNameCommandActive != null) {
44         CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive;
45       }
46       if (editor.config.classNameActionActive != null) {
47         CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive;
48       }
49
50       var speechInputLinks  = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
51           length            = speechInputLinks.length,
52           i                 = 0;
53       for (; i<length; i++) {
54         new wysihtml5.toolbar.Speech(this, speechInputLinks[i]);
55       }
56     },
57
58     _getLinks: function(type) {
59       var links   = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("[data-wysihtml5-" + type + "]")).get(),
60           length  = links.length,
61           i       = 0,
62           mapping = this[type + "Mapping"] = {},
63           link,
64           group,
65           name,
66           value,
67           dialog;
68       for (; i<length; i++) {
69         link    = links[i];
70         name    = link.getAttribute("data-wysihtml5-" + type);
71         value   = link.getAttribute("data-wysihtml5-" + type + "-value");
72         group   = this.container.querySelector("[data-wysihtml5-" + type + "-group='" + name + "']");
73         dialog  = this._getDialog(link, name);
74
75         mapping[name + ":" + value] = {
76           link:   link,
77           group:  group,
78           name:   name,
79           value:  value,
80           dialog: dialog,
81           state:  false
82         };
83       }
84     },
85
86     _getDialog: function(link, command) {
87       var that          = this,
88           dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
89           dialog,
90           caretBookmark;
91
92       if (dialogElement) {
93         if (wysihtml5.toolbar["Dialog_" + command]) {
94             dialog = new wysihtml5.toolbar["Dialog_" + command](link, dialogElement);
95         } else {
96             dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
97         }
98
99         dialog.on("show", function() {
100           caretBookmark = that.composer.selection.getBookmark();
101
102           that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
103         });
104
105         dialog.on("save", function(attributes) {
106           if (caretBookmark) {
107             that.composer.selection.setBookmark(caretBookmark);
108           }
109           that._execCommand(command, attributes);
110
111           that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
112         });
113
114         dialog.on("cancel", function() {
115           that.editor.focus(false);
116           that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
117         });
118       }
119       return dialog;
120     },
121
122     /**
123      * @example
124      *    var toolbar = new wysihtml5.Toolbar();
125      *    // Insert a <blockquote> element or wrap current selection in <blockquote>
126      *    toolbar.execCommand("formatBlock", "blockquote");
127      */
128     execCommand: function(command, commandValue) {
129       if (this.commandsDisabled) {
130         return;
131       }
132
133       var commandObj = this.commandMapping[command + ":" + commandValue];
134
135       // Show dialog when available
136       if (commandObj && commandObj.dialog && !commandObj.state) {
137         commandObj.dialog.show();
138       } else {
139         this._execCommand(command, commandValue);
140       }
141     },
142
143     _execCommand: function(command, commandValue) {
144       // Make sure that composer is focussed (false => don't move caret to the end)
145       this.editor.focus(false);
146
147       this.composer.commands.exec(command, commandValue);
148       this._updateLinkStates();
149     },
150
151     execAction: function(action) {
152       var editor = this.editor;
153       if (action === "change_view") {
154         if (editor.textarea) {
155             if (editor.currentView === editor.textarea) {
156               editor.fire("change_view", "composer");
157             } else {
158               editor.fire("change_view", "textarea");
159             }
160         }
161       }
162       if (action == "showSource") {
163           editor.fire("showSource");
164       }
165     },
166
167     _observe: function() {
168       var that      = this,
169           editor    = this.editor,
170           container = this.container,
171           links     = this.commandLinks.concat(this.actionLinks),
172           length    = links.length,
173           i         = 0;
174
175       for (; i<length; i++) {
176         // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
177         // (you know, a:link { ... } doesn't match anchors with missing href attribute)
178         if (links[i].nodeName === "A") {
179           dom.setAttributes({
180             href:         "javascript:;",
181             unselectable: "on"
182           }).on(links[i]);
183         } else {
184           dom.setAttributes({ unselectable: "on" }).on(links[i]);
185         }
186       }
187
188       // Needed for opera and chrome
189       dom.delegate(container, "[data-wysihtml5-command], [data-wysihtml5-action]", "mousedown", function(event) { event.preventDefault(); });
190
191       dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
192         var link          = this,
193             command       = link.getAttribute("data-wysihtml5-command"),
194             commandValue  = link.getAttribute("data-wysihtml5-command-value");
195         that.execCommand(command, commandValue);
196         event.preventDefault();
197       });
198
199       dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) {
200         var action = this.getAttribute("data-wysihtml5-action");
201         that.execAction(action);
202         event.preventDefault();
203       });
204
205       editor.on("interaction:composer", function() {
206           that._updateLinkStates();
207       });
208
209       editor.on("focus:composer", function() {
210         that.bookmark = null;
211       });
212
213       if (this.editor.config.handleTables) {
214           editor.on("tableselect:composer", function() {
215               that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
216           });
217           editor.on("tableunselect:composer", function() {
218               that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
219           });
220       }
221
222       editor.on("change_view", function(currentView) {
223         // Set timeout needed in order to let the blur event fire first
224         if (editor.textarea) {
225             setTimeout(function() {
226               that.commandsDisabled = (currentView !== "composer");
227               that._updateLinkStates();
228               if (that.commandsDisabled) {
229                 dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
230               } else {
231                 dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
232               }
233             }, 0);
234         }
235       });
236     },
237
238     _updateLinkStates: function() {
239
240       var commandMapping    = this.commandMapping,
241           actionMapping     = this.actionMapping,
242           i,
243           state,
244           action,
245           command;
246       // every millisecond counts... this is executed quite often
247       for (i in commandMapping) {
248         command = commandMapping[i];
249         if (this.commandsDisabled) {
250           state = false;
251           dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
252           if (command.group) {
253             dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
254           }
255           if (command.dialog) {
256             command.dialog.hide();
257           }
258         } else {
259           state = this.composer.commands.state(command.name, command.value);
260           dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
261           if (command.group) {
262             dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED);
263           }
264         }
265         if (command.state === state) {
266           continue;
267         }
268
269         command.state = state;
270         if (state) {
271           dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
272           if (command.group) {
273             dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
274           }
275           if (command.dialog) {
276             if (typeof(state) === "object" || wysihtml5.lang.object(state).isArray()) {
277
278               if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
279                 // Grab first and only object/element in state array, otherwise convert state into boolean
280                 // to avoid showing a dialog for multiple selected elements which may have different attributes
281                 // eg. when two links with different href are selected, the state will be an array consisting of both link elements
282                 // but the dialog interface can only update one
283                 state = state.length === 1 ? state[0] : true;
284                 command.state = state;
285               }
286               command.dialog.show(state);
287             } else {
288               command.dialog.hide();
289             }
290           }
291         } else {
292           dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
293           if (command.group) {
294             dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
295           }
296           if (command.dialog) {
297             command.dialog.hide();
298           }
299         }
300       }
301
302       for (i in actionMapping) {
303         action = actionMapping[i];
304
305         if (action.name === "change_view") {
306           action.state = this.editor.currentView === this.editor.textarea;
307           if (action.state) {
308             dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE);
309           } else {
310             dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE);
311           }
312         }
313       }
314     },
315
316     show: function() {
317       this.container.style.display = "";
318     },
319
320     hide: function() {
321       this.container.style.display = "none";
322     }
323   });
324
325 })(wysihtml5);