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.
- checksums.yaml +4 -4
- data/README.md +9 -6
- data/lib/wysihtml/rails/version.rb +1 -1
- data/vendor/assets/javascripts/wysihtml.js +5944 -8553
- data/vendor/assets/javascripts/wysihtml/all_commands.js +23 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/alignCenterStyle.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/alignJustifyStyle.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/alignLeftStyle.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/alignRightStyle.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/bgColorStyle.js +48 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/bold.js +16 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/command_formatCode.js +52 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/command_insertImage.js +108 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/fontSize.js +13 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/fontSizeStyle.js +35 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/foreColor.js +13 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/foreColorStyle.js +52 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/insertBlockQuote.js +16 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/insertOrderedList.js +11 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/insertUnorderedList.js +11 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/italic.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/justifyCenter.js +18 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/justifyFull.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/justifyLeft.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/justifyRight.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/subscript.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/superscript.js +17 -0
- data/vendor/assets/javascripts/wysihtml/extra_commands/underline.js +17 -0
- data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/advanced.js +4 -4
- data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/advanced_and_extended.js +25 -25
- data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/advanced_unwrap.js +5 -5
- data/vendor/assets/javascripts/{parser_rules → wysihtml/parser_rules}/simple.js +2 -2
- data/vendor/assets/javascripts/wysihtml/table_editing.js +1163 -0
- data/vendor/assets/javascripts/wysihtml/toolbar.js +850 -0
- metadata +35 -9
- 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);
|