wysihtml-rails 0.5.5 → 0.6.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);