wysihtml-rails 0.5.5 → 0.6.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -6
  3. data/lib/wysihtml/rails/version.rb +1 -1
  4. data/vendor/assets/javascripts/wysihtml.js +5944 -8553
  5. data/vendor/assets/javascripts/wysihtml/all_commands.js +23 -0
  6. data/vendor/assets/javascripts/wysihtml/extra_commands/alignCenterStyle.js +17 -0
  7. data/vendor/assets/javascripts/wysihtml/extra_commands/alignJustifyStyle.js +17 -0
  8. data/vendor/assets/javascripts/wysihtml/extra_commands/alignLeftStyle.js +17 -0
  9. data/vendor/assets/javascripts/wysihtml/extra_commands/alignRightStyle.js +17 -0
  10. data/vendor/assets/javascripts/wysihtml/extra_commands/bgColorStyle.js +48 -0
  11. data/vendor/assets/javascripts/wysihtml/extra_commands/bold.js +16 -0
  12. data/vendor/assets/javascripts/wysihtml/extra_commands/command_formatCode.js +52 -0
  13. data/vendor/assets/javascripts/wysihtml/extra_commands/command_insertImage.js +108 -0
  14. data/vendor/assets/javascripts/wysihtml/extra_commands/fontSize.js +13 -0
  15. data/vendor/assets/javascripts/wysihtml/extra_commands/fontSizeStyle.js +35 -0
  16. data/vendor/assets/javascripts/wysihtml/extra_commands/foreColor.js +13 -0
  17. data/vendor/assets/javascripts/wysihtml/extra_commands/foreColorStyle.js +52 -0
  18. data/vendor/assets/javascripts/wysihtml/extra_commands/insertBlockQuote.js +16 -0
  19. data/vendor/assets/javascripts/wysihtml/extra_commands/insertOrderedList.js +11 -0
  20. data/vendor/assets/javascripts/wysihtml/extra_commands/insertUnorderedList.js +11 -0
  21. data/vendor/assets/javascripts/wysihtml/extra_commands/italic.js +17 -0
  22. data/vendor/assets/javascripts/wysihtml/extra_commands/justifyCenter.js +18 -0
  23. data/vendor/assets/javascripts/wysihtml/extra_commands/justifyFull.js +17 -0
  24. data/vendor/assets/javascripts/wysihtml/extra_commands/justifyLeft.js +17 -0
  25. data/vendor/assets/javascripts/wysihtml/extra_commands/justifyRight.js +17 -0
  26. data/vendor/assets/javascripts/wysihtml/extra_commands/subscript.js +17 -0
  27. data/vendor/assets/javascripts/wysihtml/extra_commands/superscript.js +17 -0
  28. data/vendor/assets/javascripts/wysihtml/extra_commands/underline.js +17 -0
  29. data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/advanced.js +4 -4
  30. data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/advanced_and_extended.js +25 -25
  31. data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/advanced_unwrap.js +5 -5
  32. data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/simple.js +2 -2
  33. data/vendor/assets/javascripts/wysihtml/table_editing.js +1163 -0
  34. data/vendor/assets/javascripts/wysihtml/toolbar.js +850 -0
  35. metadata +35 -9
  36. data/vendor/assets/javascripts/wysihtml-toolbar.js +0 -19308
@@ -0,0 +1,850 @@
1
+ /**
2
+ * Toolbar Dialog
3
+ *
4
+ * @param {Element} link The toolbar link which causes the dialog to show up
5
+ * @param {Element} container The dialog container
6
+ *
7
+ * @example
8
+ * <!-- Toolbar link -->
9
+ * <a data-wysihtml-command="insertImage">insert an image</a>
10
+ *
11
+ * <!-- Dialog -->
12
+ * <div data-wysihtml-dialog="insertImage" style="display: none;">
13
+ * <label>
14
+ * URL: <input data-wysihtml-dialog-field="src" value="http://">
15
+ * </label>
16
+ * <label>
17
+ * Alternative text: <input data-wysihtml-dialog-field="alt" value="">
18
+ * </label>
19
+ * </div>
20
+ *
21
+ * <script>
22
+ * var dialog = new wysihtml.toolbar.Dialog(
23
+ * document.querySelector("[data-wysihtml-command='insertImage']"),
24
+ * document.querySelector("[data-wysihtml-dialog='insertImage']")
25
+ * );
26
+ * dialog.observe("save", function(attributes) {
27
+ * // do something
28
+ * });
29
+ * </script>
30
+ */
31
+ (function(wysihtml) {
32
+ var dom = wysihtml.dom,
33
+ CLASS_NAME_OPENED = "wysihtml-command-dialog-opened",
34
+ SELECTOR_FORM_ELEMENTS = "input, select, textarea",
35
+ SELECTOR_FIELDS = "[data-wysihtml-dialog-field]",
36
+ ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field";
37
+
38
+
39
+ wysihtml.toolbar.Dialog = wysihtml.lang.Dispatcher.extend(
40
+ /** @scope wysihtml.toolbar.Dialog.prototype */ {
41
+ constructor: function(link, container) {
42
+ this.link = link;
43
+ this.container = container;
44
+ },
45
+
46
+ _observe: function() {
47
+ if (this._observed) {
48
+ return;
49
+ }
50
+
51
+ var that = this,
52
+ callbackWrapper = function(event) {
53
+ var attributes = that._serialize();
54
+ that.fire("save", attributes);
55
+ that.hide();
56
+ event.preventDefault();
57
+ event.stopPropagation();
58
+ };
59
+
60
+ dom.observe(that.link, "click", function() {
61
+ if (dom.hasClass(that.link, CLASS_NAME_OPENED)) {
62
+ setTimeout(function() { that.hide(); }, 0);
63
+ }
64
+ });
65
+
66
+ dom.observe(this.container, "keydown", function(event) {
67
+ var keyCode = event.keyCode;
68
+ if (keyCode === wysihtml.ENTER_KEY) {
69
+ callbackWrapper(event);
70
+ }
71
+ if (keyCode === wysihtml.ESCAPE_KEY) {
72
+ that.cancel();
73
+ }
74
+ });
75
+
76
+ dom.delegate(this.container, "[data-wysihtml-dialog-action=save]", "click", callbackWrapper);
77
+
78
+ dom.delegate(this.container, "[data-wysihtml-dialog-action=cancel]", "click", function(event) {
79
+ that.cancel();
80
+ event.preventDefault();
81
+ event.stopPropagation();
82
+ });
83
+
84
+ this._observed = true;
85
+ },
86
+
87
+ /**
88
+ * Grabs all fields in the dialog and puts them in key=>value style in an object which
89
+ * then gets returned
90
+ */
91
+ _serialize: function() {
92
+ var data = {},
93
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
94
+ length = fields.length,
95
+ i = 0;
96
+
97
+ for (; i<length; i++) {
98
+ data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
99
+ }
100
+ return data;
101
+ },
102
+
103
+ /**
104
+ * Takes the attributes of the "elementToChange"
105
+ * and inserts them in their corresponding dialog input fields
106
+ *
107
+ * Assume the "elementToChange" looks like this:
108
+ * <a href="http://www.google.com" target="_blank">foo</a>
109
+ *
110
+ * and we have the following dialog:
111
+ * <input type="text" data-wysihtml-dialog-field="href" value="">
112
+ * <input type="text" data-wysihtml-dialog-field="target" value="">
113
+ *
114
+ * after calling _interpolate() the dialog will look like this
115
+ * <input type="text" data-wysihtml-dialog-field="href" value="http://www.google.com">
116
+ * <input type="text" data-wysihtml-dialog-field="target" value="_blank">
117
+ *
118
+ * Basically it adopted the attribute values into the corresponding input fields
119
+ *
120
+ */
121
+ _interpolate: function(avoidHiddenFields) {
122
+ var field,
123
+ fieldName,
124
+ newValue,
125
+ focusedElement = document.querySelector(":focus"),
126
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
127
+ length = fields.length,
128
+ i = 0;
129
+ for (; i<length; i++) {
130
+ field = fields[i];
131
+
132
+ // Never change elements where the user is currently typing in
133
+ if (field === focusedElement) {
134
+ continue;
135
+ }
136
+
137
+ // Don't update hidden fields
138
+ // See https://github.com/xing/wysihtml5/pull/14
139
+ if (avoidHiddenFields && field.type === "hidden") {
140
+ continue;
141
+ }
142
+
143
+ fieldName = field.getAttribute(ATTRIBUTE_FIELDS);
144
+ newValue = (this.elementToChange && typeof(this.elementToChange) !== 'boolean') ? (this.elementToChange.getAttribute(fieldName) || "") : field.defaultValue;
145
+ field.value = newValue;
146
+ }
147
+ },
148
+
149
+ update: function (elementToChange) {
150
+ this.elementToChange = elementToChange ? elementToChange : this.elementToChange;
151
+ this._interpolate();
152
+ },
153
+
154
+ /**
155
+ * Show the dialog element
156
+ */
157
+ show: function(elementToChange) {
158
+ var firstField = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
159
+
160
+ this._observe();
161
+ this.update(elementToChange);
162
+
163
+ dom.addClass(this.link, CLASS_NAME_OPENED);
164
+ this.container.style.display = "";
165
+ this.isOpen = true;
166
+ this.fire("show");
167
+
168
+ if (firstField && !elementToChange) {
169
+ try {
170
+ firstField.focus();
171
+ } catch(e) {}
172
+ }
173
+ },
174
+
175
+ /**
176
+ * Hide the dialog element
177
+ */
178
+ _hide: function(focus) {
179
+ this.elementToChange = null;
180
+ dom.removeClass(this.link, CLASS_NAME_OPENED);
181
+ this.container.style.display = "none";
182
+ this.isOpen = false;
183
+ },
184
+
185
+ hide: function() {
186
+ this._hide();
187
+ this.fire("hide");
188
+ },
189
+
190
+ cancel: function() {
191
+ this._hide();
192
+ this.fire("cancel");
193
+ }
194
+ });
195
+ })(wysihtml); //jshint ignore:line
196
+
197
+ (function(wysihtml) {
198
+ var dom = wysihtml.dom,
199
+ SELECTOR_FIELDS = "[data-wysihtml-dialog-field]",
200
+ ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field";
201
+
202
+ wysihtml.toolbar.Dialog_bgColorStyle = wysihtml.toolbar.Dialog.extend({
203
+ multiselect: true,
204
+
205
+ _serialize: function() {
206
+ var data = {},
207
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
208
+ length = fields.length,
209
+ i = 0;
210
+
211
+ for (; i<length; i++) {
212
+ data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
213
+ }
214
+ return data;
215
+ },
216
+
217
+ _interpolate: function(avoidHiddenFields) {
218
+ var field,
219
+ fieldName,
220
+ newValue,
221
+ focusedElement = document.querySelector(":focus"),
222
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
223
+ length = fields.length,
224
+ i = 0,
225
+ firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
226
+ colorStr = (firstElement) ? firstElement.getAttribute('style') : null,
227
+ color = (colorStr) ? wysihtml.quirks.styleParser.parseColor(colorStr, "background-color") : null;
228
+
229
+ for (; i<length; i++) {
230
+ field = fields[i];
231
+ // Never change elements where the user is currently typing in
232
+ if (field === focusedElement) {
233
+ continue;
234
+ }
235
+ // Don't update hidden fields3
236
+ if (avoidHiddenFields && field.type === "hidden") {
237
+ continue;
238
+ }
239
+ if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") {
240
+ if (color) {
241
+ if (color[3] && color[3] != 1) {
242
+ field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");";
243
+ } else {
244
+ field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");";
245
+ }
246
+ } else {
247
+ field.value = "rgb(0,0,0);";
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ });
254
+ })(wysihtml);
255
+
256
+ (function(wysihtml) {
257
+ wysihtml.toolbar.Dialog_createTable = wysihtml.toolbar.Dialog.extend({
258
+ show: function(elementToChange) {
259
+ this.base(elementToChange);
260
+ }
261
+ });
262
+ })(wysihtml);
263
+
264
+ (function(wysihtml) {
265
+ var dom = wysihtml.dom,
266
+ SELECTOR_FIELDS = "[data-wysihtml-dialog-field]",
267
+ ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field";
268
+
269
+ wysihtml.toolbar.Dialog_fontSizeStyle = wysihtml.toolbar.Dialog.extend({
270
+ multiselect: true,
271
+
272
+ _serialize: function() {
273
+ return {"size" : this.container.querySelector('[data-wysihtml-dialog-field="size"]').value};
274
+ },
275
+
276
+ _interpolate: function(avoidHiddenFields) {
277
+ var focusedElement = document.querySelector(":focus"),
278
+ field = this.container.querySelector("[data-wysihtml-dialog-field='size']"),
279
+ firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
280
+ styleStr = (firstElement) ? firstElement.getAttribute('style') : null,
281
+ size = (styleStr) ? wysihtml.quirks.styleParser.parseFontSize(styleStr) : null;
282
+
283
+ if (field && field !== focusedElement && size && !(/^\s*$/).test(size)) {
284
+ field.value = size;
285
+ }
286
+ }
287
+ });
288
+ })(wysihtml);
289
+
290
+ (function(wysihtml) {
291
+ var SELECTOR_FIELDS = "[data-wysihtml-dialog-field]",
292
+ ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field";
293
+
294
+ wysihtml.toolbar.Dialog_foreColorStyle = wysihtml.toolbar.Dialog.extend({
295
+ multiselect: true,
296
+
297
+ _serialize: function() {
298
+ var data = {},
299
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
300
+ length = fields.length,
301
+ i = 0;
302
+
303
+ for (; i<length; i++) {
304
+ data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
305
+ }
306
+ return data;
307
+ },
308
+
309
+ _interpolate: function(avoidHiddenFields) {
310
+ var field, colourMode,
311
+ styleParser = wysihtml.quirks.styleParser,
312
+ focusedElement = document.querySelector(":focus"),
313
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
314
+ length = fields.length,
315
+ i = 0,
316
+ firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
317
+ colourStr = (firstElement) ? firstElement.getAttribute("style") : null,
318
+ colour = (colourStr) ? styleParser.parseColor(colourStr, "color") : null;
319
+
320
+ for (; i<length; i++) {
321
+ field = fields[i];
322
+ // Never change elements where the user is currently typing in
323
+ if (field === focusedElement) {
324
+ continue;
325
+ }
326
+ // Don't update hidden fields3
327
+ if (avoidHiddenFields && field.type === "hidden") {
328
+ continue;
329
+ }
330
+ if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") {
331
+ colourMode = (field.dataset.colormode || "rgb").toLowerCase();
332
+ colourMode = colourMode === "hex" ? "hash" : colourMode;
333
+
334
+ if (colour) {
335
+ field.value = styleParser.unparseColor(colour, colourMode);
336
+ } else {
337
+ field.value = styleParser.unparseColor([0, 0, 0], colourMode);
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ });
344
+ })(wysihtml);
345
+
346
+ /**
347
+ * Converts speech-to-text and inserts this into the editor
348
+ * As of now (2011/03/25) this only is supported in Chrome >= 11
349
+ *
350
+ * Note that it sends the recorded audio to the google speech recognition api:
351
+ * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec
352
+ *
353
+ * Current HTML5 draft can be found here
354
+ * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html
355
+ *
356
+ * "Accessing Google Speech API Chrome 11"
357
+ * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
358
+ */
359
+ (function(wysihtml) {
360
+ var dom = wysihtml.dom;
361
+
362
+ var linkStyles = {
363
+ position: "relative"
364
+ };
365
+
366
+ var wrapperStyles = {
367
+ left: 0,
368
+ margin: 0,
369
+ opacity: 0,
370
+ overflow: "hidden",
371
+ padding: 0,
372
+ position: "absolute",
373
+ top: 0,
374
+ zIndex: 1
375
+ };
376
+
377
+ var inputStyles = {
378
+ cursor: "inherit",
379
+ fontSize: "50px",
380
+ height: "50px",
381
+ marginTop: "-25px",
382
+ outline: 0,
383
+ padding: 0,
384
+ position: "absolute",
385
+ right: "-4px",
386
+ top: "50%"
387
+ };
388
+
389
+ var inputAttributes = {
390
+ "x-webkit-speech": "",
391
+ "speech": ""
392
+ };
393
+
394
+ wysihtml.toolbar.Speech = function(parent, link) {
395
+ var input = document.createElement("input");
396
+ if (!wysihtml.browser.supportsSpeechApiOn(input)) {
397
+ link.style.display = "none";
398
+ return;
399
+ }
400
+ var lang = parent.editor.textarea.element.getAttribute("lang");
401
+ if (lang) {
402
+ inputAttributes.lang = lang;
403
+ }
404
+
405
+ var wrapper = document.createElement("div");
406
+
407
+ wysihtml.lang.object(wrapperStyles).merge({
408
+ width: link.offsetWidth + "px",
409
+ height: link.offsetHeight + "px"
410
+ });
411
+
412
+ dom.insert(input).into(wrapper);
413
+ dom.insert(wrapper).into(link);
414
+
415
+ dom.setStyles(inputStyles).on(input);
416
+ dom.setAttributes(inputAttributes).on(input);
417
+
418
+ dom.setStyles(wrapperStyles).on(wrapper);
419
+ dom.setStyles(linkStyles).on(link);
420
+
421
+ var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange";
422
+ dom.observe(input, eventName, function() {
423
+ parent.execCommand("insertText", input.value);
424
+ input.value = "";
425
+ });
426
+
427
+ dom.observe(input, "click", function(event) {
428
+ if (dom.hasClass(link, "wysihtml-command-disabled")) {
429
+ event.preventDefault();
430
+ }
431
+
432
+ event.stopPropagation();
433
+ });
434
+ };
435
+ })(wysihtml);
436
+
437
+ /**
438
+ * Toolbar
439
+ *
440
+ * @param {Object} parent Reference to instance of Editor instance
441
+ * @param {Element} container Reference to the toolbar container element
442
+ *
443
+ * @example
444
+ * <div id="toolbar">
445
+ * <a data-wysihtml-command="createLink">insert link</a>
446
+ * <a data-wysihtml-command="formatBlock" data-wysihtml-command-value="h1">insert h1</a>
447
+ * </div>
448
+ *
449
+ * <script>
450
+ * var toolbar = new wysihtml.toolbar.Toolbar(editor, document.getElementById("toolbar"));
451
+ * </script>
452
+ */
453
+ (function(wysihtml) {
454
+ var CLASS_NAME_COMMAND_DISABLED = "wysihtml-command-disabled",
455
+ CLASS_NAME_COMMANDS_DISABLED = "wysihtml-commands-disabled",
456
+ CLASS_NAME_COMMAND_ACTIVE = "wysihtml-command-active",
457
+ CLASS_NAME_ACTION_ACTIVE = "wysihtml-action-active",
458
+ dom = wysihtml.dom;
459
+
460
+ wysihtml.toolbar.Toolbar = Base.extend(
461
+ /** @scope wysihtml.toolbar.Toolbar.prototype */ {
462
+ constructor: function(editor, container, showOnInit) {
463
+ this.editor = editor;
464
+ this.container = typeof(container) === "string" ? document.getElementById(container) : container;
465
+ this.composer = editor.composer;
466
+
467
+ this._getLinks("command");
468
+ this._getLinks("action");
469
+
470
+ this._observe();
471
+ if (showOnInit) { this.show(); }
472
+
473
+ if (editor.config.classNameCommandDisabled != null) {
474
+ CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled;
475
+ }
476
+ if (editor.config.classNameCommandsDisabled != null) {
477
+ CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled;
478
+ }
479
+ if (editor.config.classNameCommandActive != null) {
480
+ CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive;
481
+ }
482
+ if (editor.config.classNameActionActive != null) {
483
+ CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive;
484
+ }
485
+
486
+ var speechInputLinks = this.container.querySelectorAll("[data-wysihtml-command=insertSpeech]"),
487
+ length = speechInputLinks.length,
488
+ i = 0;
489
+ for (; i<length; i++) {
490
+ new wysihtml.toolbar.Speech(this, speechInputLinks[i]);
491
+ }
492
+ },
493
+
494
+ _getLinks: function(type) {
495
+ var links = this[type + "Links"] = wysihtml.lang.array(this.container.querySelectorAll("[data-wysihtml-" + type + "]")).get(),
496
+ length = links.length,
497
+ i = 0,
498
+ mapping = this[type + "Mapping"] = {},
499
+ link,
500
+ group,
501
+ name,
502
+ value,
503
+ dialog,
504
+ tracksBlankValue;
505
+
506
+ for (; i<length; i++) {
507
+ link = links[i];
508
+ name = link.getAttribute("data-wysihtml-" + type);
509
+ value = link.getAttribute("data-wysihtml-" + type + "-value");
510
+ tracksBlankValue = link.getAttribute("data-wysihtml-" + type + "-blank-value");
511
+ group = this.container.querySelector("[data-wysihtml-" + type + "-group='" + name + "']");
512
+ dialog = this._getDialog(link, name);
513
+
514
+ mapping[name + ":" + value] = {
515
+ link: link,
516
+ group: group,
517
+ name: name,
518
+ value: value,
519
+ tracksBlankValue: tracksBlankValue,
520
+ dialog: dialog,
521
+ state: false
522
+ };
523
+ }
524
+ },
525
+
526
+ _getDialog: function(link, command) {
527
+ var that = this,
528
+ dialogElement = this.container.querySelector("[data-wysihtml-dialog='" + command + "']"),
529
+ dialog, caretBookmark;
530
+
531
+ if (dialogElement) {
532
+ if (wysihtml.toolbar["Dialog_" + command]) {
533
+ dialog = new wysihtml.toolbar["Dialog_" + command](link, dialogElement);
534
+ } else {
535
+ dialog = new wysihtml.toolbar.Dialog(link, dialogElement);
536
+ }
537
+
538
+ dialog.on("show", function() {
539
+ caretBookmark = that.composer.selection.getBookmark();
540
+ that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
541
+ });
542
+
543
+ dialog.on("save", function(attributes) {
544
+ if (caretBookmark) {
545
+ that.composer.selection.setBookmark(caretBookmark);
546
+ }
547
+ that._execCommand(command, attributes);
548
+ that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
549
+ that._hideAllDialogs();
550
+ that._preventInstantFocus();
551
+ caretBookmark = undefined;
552
+
553
+ });
554
+
555
+ dialog.on("cancel", function() {
556
+ if (caretBookmark) {
557
+ that.composer.selection.setBookmark(caretBookmark);
558
+ }
559
+ that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
560
+ caretBookmark = undefined;
561
+ that._preventInstantFocus();
562
+ });
563
+
564
+ dialog.on("hide", function() {
565
+ that.editor.fire("hide:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
566
+ caretBookmark = undefined;
567
+ });
568
+
569
+ }
570
+ return dialog;
571
+ },
572
+
573
+ /**
574
+ * @example
575
+ * var toolbar = new wysihtml.Toolbar();
576
+ * // Insert a <blockquote> element or wrap current selection in <blockquote>
577
+ * toolbar.execCommand("formatBlock", "blockquote");
578
+ */
579
+ execCommand: function(command, commandValue) {
580
+ if (this.commandsDisabled) {
581
+ return;
582
+ }
583
+
584
+ this._execCommand(command, commandValue);
585
+ },
586
+
587
+ _execCommand: function(command, commandValue) {
588
+ // Make sure that composer is focussed (false => don't move caret to the end)
589
+ this.editor.focus(false);
590
+
591
+ this.composer.commands.exec(command, commandValue);
592
+ this._updateLinkStates();
593
+ },
594
+
595
+ execAction: function(action) {
596
+ var editor = this.editor;
597
+ if (action === "change_view") {
598
+ if (editor.currentView === editor.textarea || editor.currentView === "source") {
599
+ editor.fire("change_view", "composer");
600
+ } else {
601
+ editor.fire("change_view", "textarea");
602
+ }
603
+ }
604
+ if (action == "showSource") {
605
+ editor.fire("showSource");
606
+ }
607
+ },
608
+
609
+ _observe: function() {
610
+ var that = this,
611
+ editor = this.editor,
612
+ container = this.container,
613
+ links = this.commandLinks.concat(this.actionLinks),
614
+ length = links.length,
615
+ i = 0;
616
+
617
+ for (; i<length; i++) {
618
+ // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
619
+ // (you know, a:link { ... } doesn't match anchors with missing href attribute)
620
+ if (links[i].nodeName === "A") {
621
+ dom.setAttributes({
622
+ href: "javascript:;",
623
+ unselectable: "on"
624
+ }).on(links[i]);
625
+ } else {
626
+ dom.setAttributes({ unselectable: "on" }).on(links[i]);
627
+ }
628
+ }
629
+
630
+ // Needed for opera and chrome
631
+ dom.delegate(container, "[data-wysihtml-command], [data-wysihtml-action]", "mousedown", function(event) { event.preventDefault(); });
632
+
633
+ dom.delegate(container, "[data-wysihtml-command]", "click", function(event) {
634
+ var state,
635
+ link = this,
636
+ command = link.getAttribute("data-wysihtml-command"),
637
+ commandValue = link.getAttribute("data-wysihtml-command-value"),
638
+ commandObj = that.commandMapping[command + ":" + commandValue];
639
+
640
+ if (commandValue || !commandObj.dialog) {
641
+ that.execCommand(command, commandValue);
642
+ } else {
643
+ state = getCommandState(that.composer, commandObj);
644
+ commandObj.dialog.show(state);
645
+ }
646
+
647
+ event.preventDefault();
648
+ });
649
+
650
+ dom.delegate(container, "[data-wysihtml-action]", "click", function(event) {
651
+ var action = this.getAttribute("data-wysihtml-action");
652
+ that.execAction(action);
653
+ event.preventDefault();
654
+ });
655
+
656
+ editor.on("interaction:composer", function(event) {
657
+ if (!that.preventFocus) {
658
+ that._updateLinkStates();
659
+ }
660
+ });
661
+
662
+ this._ownerDocumentClick = function(event) {
663
+ if (!wysihtml.dom.contains(that.container, event.target) && !wysihtml.dom.contains(that.composer.element, event.target)) {
664
+ that._updateLinkStates();
665
+ that._preventInstantFocus();
666
+ }
667
+ };
668
+
669
+ this.container.ownerDocument.addEventListener("click", this._ownerDocumentClick, false);
670
+ this.editor.on("destroy:composer", this.destroy.bind(this));
671
+
672
+ if (this.editor.config.handleTables) {
673
+ editor.on("tableselect:composer", function() {
674
+ that.container.querySelectorAll('[data-wysihtml-hiddentools="table"]')[0].style.display = "";
675
+ });
676
+ editor.on("tableunselect:composer", function() {
677
+ that.container.querySelectorAll('[data-wysihtml-hiddentools="table"]')[0].style.display = "none";
678
+ });
679
+ }
680
+
681
+ editor.on("change_view", function(currentView) {
682
+ // Set timeout needed in order to let the blur event fire first
683
+ setTimeout(function() {
684
+ that.commandsDisabled = (currentView !== "composer");
685
+ that._updateLinkStates();
686
+ if (that.commandsDisabled) {
687
+ dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
688
+ } else {
689
+ dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
690
+ }
691
+ }, 0);
692
+ });
693
+ },
694
+
695
+ destroy: function() {
696
+ this.container.ownerDocument.removeEventListener("click", this._ownerDocumentClick, false);
697
+ },
698
+
699
+ _hideAllDialogs: function() {
700
+ var commandMapping = this.commandMapping;
701
+ for (var i in commandMapping) {
702
+ if (commandMapping[i].dialog) {
703
+ commandMapping[i].dialog.hide();
704
+ }
705
+ }
706
+ },
707
+
708
+ _preventInstantFocus: function() {
709
+ this.preventFocus = true;
710
+ setTimeout(function() {
711
+ this.preventFocus = false;
712
+ }.bind(this),0);
713
+ },
714
+
715
+ _updateLinkStates: function() {
716
+
717
+ var i, state, action, command, displayDialogAttributeValue,
718
+ commandMapping = this.commandMapping,
719
+ composer = this.composer,
720
+ actionMapping = this.actionMapping;
721
+ // every millisecond counts... this is executed quite often
722
+ for (i in commandMapping) {
723
+ command = commandMapping[i];
724
+ if (this.commandsDisabled) {
725
+ state = false;
726
+ dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
727
+ if (command.group) {
728
+ dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
729
+ }
730
+ if (command.dialog) {
731
+ command.dialog.hide();
732
+ }
733
+ } else {
734
+ state = this.composer.commands.state(command.name, command.value);
735
+ dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
736
+ if (command.group) {
737
+ dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED);
738
+ }
739
+ }
740
+ if (command.state === state && !command.tracksBlankValue) {
741
+ continue;
742
+ }
743
+
744
+ command.state = state;
745
+ if (state) {
746
+ if (command.tracksBlankValue) {
747
+ dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
748
+ } else {
749
+ dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
750
+ if (command.group) {
751
+ dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
752
+ }
753
+ // commands with fixed value can not have a dialog.
754
+ if (command.dialog && (typeof command.value === "undefined" || command.value === null)) {
755
+ if (state && typeof state === "object") {
756
+ state = getCommandState(composer, command);
757
+ command.state = state;
758
+
759
+ // If dialog has dataset.showdialogonselection set as true,
760
+ // Dialog displays on text state becoming active regardless of clobal showToolbarDialogsOnSelection options value
761
+ displayDialogAttributeValue = command.dialog.container.dataset ? command.dialog.container.dataset.showdialogonselection : false;
762
+
763
+ if (composer.config.showToolbarDialogsOnSelection || displayDialogAttributeValue) {
764
+ command.dialog.show(state);
765
+ } else {
766
+ command.dialog.update(state);
767
+ }
768
+ } else {
769
+ command.dialog.hide();
770
+ }
771
+ }
772
+ }
773
+ } else {
774
+ if (command.tracksBlankValue) {
775
+ dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
776
+ } else {
777
+ dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
778
+ if (command.group) {
779
+ dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
780
+ }
781
+ // commands with fixed value can not have a dialog.
782
+ if (command.dialog && !command.value) {
783
+ command.dialog.hide();
784
+ }
785
+ }
786
+ }
787
+ }
788
+
789
+ for (i in actionMapping) {
790
+ action = actionMapping[i];
791
+
792
+ if (action.name === "change_view") {
793
+ action.state = this.editor.currentView === this.editor.textarea || this.editor.currentView === "source";
794
+ if (action.state) {
795
+ dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE);
796
+ } else {
797
+ dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE);
798
+ }
799
+ }
800
+ }
801
+ },
802
+
803
+ show: function() {
804
+ this.container.style.display = "";
805
+ },
806
+
807
+ hide: function() {
808
+ this.container.style.display = "none";
809
+ }
810
+ });
811
+
812
+ function getCommandState (composer, command) {
813
+ var state = composer.commands.state(command.name, command.value);
814
+
815
+ // Grab first and only object/element in state array, otherwise convert state into boolean
816
+ // to avoid showing a dialog for multiple selected elements which may have different attributes
817
+ // eg. when two links with different href are selected, the state will be an array consisting of both link elements
818
+ // but the dialog interface can only update one
819
+ if (!command.dialog.multiselect && wysihtml.lang.object(state).isArray()) {
820
+ state = state.length === 1 ? state[0] : true;
821
+ }
822
+
823
+ return state;
824
+ }
825
+
826
+ // Extend defaults
827
+
828
+ // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
829
+ wysihtml.Editor.prototype.defaults.toolbar = undefined;
830
+
831
+ // Whether toolbar is displayed after init by script automatically.
832
+ // Can be set to false if toolobar is set to display only on editable area focus
833
+ wysihtml.Editor.prototype.defaults.showToolbarAfterInit = true;
834
+
835
+ // With default toolbar it shows dialogs in toolbar when their related text format state becomes active (click on link in text opens link dialogue)
836
+ wysihtml.Editor.prototype.defaults.showToolbarDialogsOnSelection= true;
837
+
838
+ // Bind toolbar initiation on editor instance creation
839
+ wysihtml.extendEditor(function(editor) {
840
+ if (editor.config.toolbar) {
841
+ editor.toolbar = new wysihtml.toolbar.Toolbar(editor, editor.config.toolbar, editor.config.showToolbarAfterInit);
842
+ editor.on('destroy:composer', function() {
843
+ if (editor && editor.toolbar) {
844
+ editor.toolbar.destroy();
845
+ }
846
+ });
847
+ }
848
+ });
849
+
850
+ })(wysihtml);