trumbowyg_rails 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +41 -0
  3. data/VERSION +1 -0
  4. data/lib/trumbowyg_rails.rb +4 -0
  5. data/trumbowyg_rails.gemspec +19 -0
  6. data/vendor/assets/images/trumbowyg/images/icons-2x.png +0 -0
  7. data/vendor/assets/images/trumbowyg/images/icons.png +0 -0
  8. data/vendor/assets/javascripts/trumbowyg/langs/ca.js +58 -0
  9. data/vendor/assets/javascripts/trumbowyg/langs/de.js +56 -0
  10. data/vendor/assets/javascripts/trumbowyg/langs/en.js +14 -0
  11. data/vendor/assets/javascripts/trumbowyg/langs/es.js +56 -0
  12. data/vendor/assets/javascripts/trumbowyg/langs/es_ar.js +56 -0
  13. data/vendor/assets/javascripts/trumbowyg/langs/fa.js +57 -0
  14. data/vendor/assets/javascripts/trumbowyg/langs/fi.js +56 -0
  15. data/vendor/assets/javascripts/trumbowyg/langs/fr.js +57 -0
  16. data/vendor/assets/javascripts/trumbowyg/langs/id.js +58 -0
  17. data/vendor/assets/javascripts/trumbowyg/langs/it.js +55 -0
  18. data/vendor/assets/javascripts/trumbowyg/langs/ko.js +57 -0
  19. data/vendor/assets/javascripts/trumbowyg/langs/pl.js +56 -0
  20. data/vendor/assets/javascripts/trumbowyg/langs/pt.js +58 -0
  21. data/vendor/assets/javascripts/trumbowyg/langs/ro.js +60 -0
  22. data/vendor/assets/javascripts/trumbowyg/langs/ru.js +55 -0
  23. data/vendor/assets/javascripts/trumbowyg/langs/tr.js +57 -0
  24. data/vendor/assets/javascripts/trumbowyg/langs/zh_cn.js +57 -0
  25. data/vendor/assets/javascripts/trumbowyg/trumbowyg.js +1096 -0
  26. data/vendor/assets/stylesheets/trumbowyg/_sprite-2x.scss +28 -0
  27. data/vendor/assets/stylesheets/trumbowyg/_sprite.scss +28 -0
  28. data/vendor/assets/stylesheets/trumbowyg/trumbowyg.scss +566 -0
  29. metadata +113 -0
@@ -0,0 +1,57 @@
1
+ /* ===========================================================
2
+ * tr.js
3
+ * Turkish translation for Trumbowyg
4
+ * http://alex-d.github.com/Trumbowyg
5
+ * ===========================================================
6
+ * Author : Emrah Bilbay (munzur)
7
+ * Github : https://github.com/munzur
8
+ * Website: http://kafe.in/
9
+ */
10
+
11
+ jQuery.trumbowyg.langs.tr = {
12
+ viewHTML: "HTML Kodu",
13
+
14
+ formatting: "Biçimlendirme",
15
+ p: "Paragraf",
16
+ blockquote: "Alıntı",
17
+ code: "Kod",
18
+ header: "Başlık",
19
+
20
+ bold: "Kalın",
21
+ italic: "İtalik",
22
+ strikethrough: "Orta çizgi",
23
+ underline: "Alt çigzi",
24
+
25
+ strong: "Koyu",
26
+ em: "Vurgulu",
27
+ del: "Üstü çizilmiş",
28
+
29
+ unorderedList: "Numarasız liste",
30
+ orderedList: "Numaralı liste",
31
+
32
+ insertImage: "Resim yerleştir",
33
+ insertVideo: "Video yerleştir",
34
+ link: "Link",
35
+ createLink: "Link yerleştir",
36
+ unlink: "Linki sil",
37
+
38
+ justifyLeft: "Sola hizala",
39
+ justifyCenter: "Ortaya hizala",
40
+ justifyRight: "Sağa hizala",
41
+ justifyFull: "Yasla",
42
+
43
+ horizontalRule: "Yatay çizgi ekle",
44
+
45
+ fullscreen: "Tam ekran",
46
+
47
+ close: "Kapat",
48
+
49
+ submit: "Onayla",
50
+ reset: "Sıfırla",
51
+
52
+ invalidUrl: "Hatalı URL",
53
+ required: "Gerekli",
54
+ description: "Açıklama",
55
+ title: "Başlık",
56
+ text: "Metin"
57
+ };
@@ -0,0 +1,57 @@
1
+ /* ===========================================================
2
+ * zh_cn.js
3
+ * Simplified Chinese translation for Trumbowyg
4
+ * http://alex-d.github.com/Trumbowyg
5
+ * ===========================================================
6
+ * Author : Liu Kai (akai)
7
+ * Twitter : @akai404
8
+ * Github : https://github.com/akai
9
+ */
10
+
11
+ jQuery.trumbowyg.langs.zh_cn = {
12
+ viewHTML: "源代码",
13
+
14
+ formatting: "格式",
15
+ p: "段落",
16
+ blockquote: "引用",
17
+ code: "代码",
18
+ header: "标题",
19
+
20
+ bold: "加粗",
21
+ italic: "斜体",
22
+ strikethrough: "删除线",
23
+ underline: "下划线",
24
+
25
+ strong: "加粗",
26
+ em: "斜体",
27
+ del: "删除线",
28
+
29
+ unorderedList: "无序列表",
30
+ orderedList: "有序列表",
31
+
32
+ insertImage: "插入图片",
33
+ insertVideo: "插入视频",
34
+ link: "超链接",
35
+ createLink: "插入链接",
36
+ unlink: "取消链接",
37
+
38
+ justifyLeft: "居左对齐",
39
+ justifyCenter: "居中对齐",
40
+ justifyRight: "居右对齐",
41
+ justifyFull: "两端对齐",
42
+
43
+ horizontalRule: "插入分隔线",
44
+
45
+ fullscreen: "全屏",
46
+
47
+ close: "关闭",
48
+
49
+ submit: "确定",
50
+ reset: "取消",
51
+
52
+ invalidUrl: "无效的 URL",
53
+ required: "必需的",
54
+ description: "描述",
55
+ title: "标题",
56
+ text: "文字"
57
+ };
@@ -0,0 +1,1096 @@
1
+ jQuery.trumbowyg = {
2
+ langs: {
3
+ en: {
4
+ viewHTML: "View HTML",
5
+
6
+ formatting: "Formatting",
7
+ p: "Paragraph",
8
+ blockquote: "Quote",
9
+ code: "Code",
10
+ header: "Header",
11
+
12
+ bold: "Bold",
13
+ italic: "Italic",
14
+ strikethrough: "Stroke",
15
+ underline: "Underline",
16
+
17
+ strong: "Strong",
18
+ em: "Emphasis",
19
+ del: "Deleted",
20
+
21
+ unorderedList: "Unordered list",
22
+ orderedList: "Ordered list",
23
+
24
+ insertImage: "Insert Image",
25
+ insertVideo: "Insert Video",
26
+ link: "Link",
27
+ createLink: "Insert link",
28
+ unlink: "Remove link",
29
+
30
+ justifyLeft: "Align Left",
31
+ justifyCenter: "Align Center",
32
+ justifyRight: "Align Right",
33
+ justifyFull: "Align Justify",
34
+
35
+ horizontalRule: "Insert horizontal rule",
36
+
37
+ fullscreen: "fullscreen",
38
+
39
+ close: "Close",
40
+
41
+ submit: "Confirm",
42
+ reset: "Cancel",
43
+
44
+ invalidUrl: "Invalid URL",
45
+ required: "Required",
46
+ description: "Description",
47
+ title: "Title",
48
+ text: "Text"
49
+ }
50
+ },
51
+
52
+ // User default options
53
+ opts: {},
54
+
55
+ btnsGrps: {
56
+ design: ['bold', 'italic', 'underline', 'strikethrough'],
57
+ semantic: ['strong', 'em', 'del'],
58
+ justify: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
59
+ lists: ['unorderedList', 'orderedList']
60
+ }
61
+ };
62
+
63
+
64
+
65
+ (function(window, document, $){
66
+ 'use strict';
67
+
68
+ // @param : o are options
69
+ // @param : p are params
70
+ $.fn.trumbowyg = function(o, p){
71
+ if(o === Object(o) || !o){
72
+ return this.each(function(){
73
+ if(!$(this).data('trumbowyg'))
74
+ $(this).data('trumbowyg', new Trumbowyg(this, o));
75
+ });
76
+ } else if(this.length === 1){
77
+ try {
78
+ var t = $(this).data('trumbowyg');
79
+ switch(o){
80
+ // Modal box
81
+ case 'openModal':
82
+ return t.openModal(p.title, p.content);
83
+ case 'closeModal':
84
+ return t.closeModal();
85
+ case 'openModalInsert':
86
+ return t.openModalInsert(p.title, p.fields, p.callback);
87
+
88
+ // Selection
89
+ case 'saveSelection':
90
+ return t.saveSelection();
91
+ case 'getSelection':
92
+ return t.selection;
93
+ case 'getSelectedText':
94
+ return t.selection+'';
95
+ case 'restoreSelection':
96
+ return t.restoreSelection();
97
+
98
+ // Destroy
99
+ case 'destroy':
100
+ return t.destroy();
101
+
102
+ // Empty
103
+ case 'empty':
104
+ return t.empty();
105
+
106
+ // Public options
107
+ case 'lang':
108
+ return t.lang;
109
+ case 'duration':
110
+ return t.o.duration;
111
+
112
+ // HTML
113
+ case 'html':
114
+ return t.html(p);
115
+ }
116
+ } catch(e){}
117
+ }
118
+
119
+ return false;
120
+ };
121
+
122
+
123
+
124
+ var Trumbowyg = function(editorElem, opts){
125
+ var t = this;
126
+ // jQuery object of the editor
127
+ t.$e = $(editorElem);
128
+ t.$creator = $(editorElem);
129
+
130
+ // Extend with options
131
+ opts = $.extend(true, {}, opts, $.trumbowyg.opts);
132
+
133
+ // Localization management
134
+ if(typeof opts.lang === 'undefined' || typeof $.trumbowyg.langs[opts.lang] === 'undefined')
135
+ t.lang = $.trumbowyg.langs.en;
136
+ else
137
+ t.lang = $.extend(true, {}, $.trumbowyg.langs.en, $.trumbowyg.langs[opts.lang]);
138
+
139
+ // Defaults Options
140
+ t.o = $.extend(true, {
141
+ lang: 'en',
142
+ dir: 'ltr',
143
+ duration: 200, // Duration of modal box animations
144
+
145
+ mobile: false,
146
+ tablet: true,
147
+ closable: false,
148
+ fullscreenable: true,
149
+ fixedBtnPane: false,
150
+ fixedFullWidth: false,
151
+ semantic: false,
152
+ resetCss: false,
153
+ autogrow: false,
154
+
155
+ prefix: 'trumbowyg-',
156
+
157
+ convertLink: true,
158
+
159
+ btns: ['viewHTML',
160
+ '|', 'formatting',
161
+ '|', $.trumbowyg.btnsGrps.design,
162
+ '|', 'link',
163
+ '|', 'insertImage',
164
+ '|', $.trumbowyg.btnsGrps.justify,
165
+ '|', $.trumbowyg.btnsGrps.lists,
166
+ '|', 'horizontalRule'],
167
+ btnsAdd: [],
168
+
169
+ /**
170
+ * When the button is associated to a empty object
171
+ * func and title attributs are defined from the button key value
172
+ *
173
+ * For example
174
+ * foo: {}
175
+ * is equivalent to :
176
+ * foo: {
177
+ * func: 'foo',
178
+ * title: this.lang.foo
179
+ * }
180
+ */
181
+ btnsDef: {
182
+ viewHTML: {
183
+ func: 'toggle'
184
+ },
185
+
186
+ p: {
187
+ func: 'formatBlock'
188
+ },
189
+ blockquote: {
190
+ func: 'formatBlock'
191
+ },
192
+ h1: {
193
+ func: 'formatBlock',
194
+ title: t.lang.header + ' 1'
195
+ },
196
+ h2: {
197
+ func: 'formatBlock',
198
+ title: t.lang.header + ' 2'
199
+ },
200
+ h3: {
201
+ func: 'formatBlock',
202
+ title: t.lang.header + ' 3'
203
+ },
204
+ h4: {
205
+ func: 'formatBlock',
206
+ title: t.lang.header + ' 4'
207
+ },
208
+
209
+ bold: {},
210
+ italic: {},
211
+ underline: {},
212
+ strikethrough: {},
213
+
214
+ strong: {
215
+ func: 'bold'
216
+ },
217
+ em: {
218
+ func: 'italic'
219
+ },
220
+ del: {
221
+ func: 'strikethrough'
222
+ },
223
+
224
+ createLink: {},
225
+ unlink: {},
226
+
227
+ insertImage: {},
228
+
229
+ justifyLeft: {},
230
+ justifyCenter: {},
231
+ justifyRight: {},
232
+ justifyFull: {},
233
+
234
+ unorderedList: {
235
+ func: 'insertUnorderedList'
236
+ },
237
+ orderedList: {
238
+ func: 'insertOrderedList'
239
+ },
240
+
241
+ horizontalRule: {
242
+ func: 'insertHorizontalRule'
243
+ },
244
+
245
+ // Dropdowns
246
+ formatting: {
247
+ dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4']
248
+ },
249
+ link: {
250
+ dropdown: ['createLink', 'unlink']
251
+ }
252
+ }
253
+ }, opts);
254
+
255
+ if(t.o.semantic && !opts.btns)
256
+ t.o.btns = [
257
+ 'viewHTML',
258
+ '|', 'formatting',
259
+ '|', $.trumbowyg.btnsGrps.semantic,
260
+ '|', 'link',
261
+ '|', 'insertImage',
262
+ '|', $.trumbowyg.btnsGrps.justify,
263
+ '|', $.trumbowyg.btnsGrps.lists,
264
+ '|', 'horizontalRule'
265
+ ];
266
+ else if(opts && opts.btns)
267
+ t.o.btns = opts.btns;
268
+
269
+ t.init();
270
+ };
271
+
272
+ Trumbowyg.prototype = {
273
+ init: function(){
274
+ var t = this;
275
+ t.height = t.$e.css('height');
276
+
277
+ if(t.isEnabled()){
278
+ t.buildEditor(true);
279
+ return;
280
+ }
281
+
282
+ t.buildEditor();
283
+ t.buildBtnPane();
284
+
285
+ t.fixedBtnPaneEvents();
286
+
287
+ t.buildOverlay();
288
+ },
289
+
290
+ buildEditor: function(disable){
291
+ var t = this;
292
+ var pfx = t.o.prefix;
293
+
294
+
295
+ if(disable === true){
296
+ if(!t.$e.is('textarea')){
297
+ var textarea = t.buildTextarea().val(t.$e.val());
298
+ t.$e.hide().after(textarea);
299
+ }
300
+ return;
301
+ }
302
+
303
+
304
+ t.$box = $('<div/>', {
305
+ class: pfx + 'box ' + pfx + t.o.lang + ' trumbowyg'
306
+ });
307
+
308
+ t.isTextarea = true;
309
+ if(t.$e.is('textarea'))
310
+ t.$editor = $('<div/>');
311
+ else {
312
+ t.$editor = t.$e;
313
+ t.$e = t.buildTextarea().val(t.$e.val());
314
+ t.isTextarea = false;
315
+ }
316
+
317
+ t.$e.hide()
318
+ .addClass(pfx + 'textarea');
319
+
320
+ var html = '';
321
+ if(t.isTextarea){
322
+ html = t.$e.val();
323
+ t.$box.insertAfter(t.$e)
324
+ .append(t.$editor)
325
+ .append(t.$e);
326
+ } else {
327
+ html = t.$editor.html();
328
+ t.$box.insertAfter(t.$editor)
329
+ .append(t.$e)
330
+ .append(t.$editor);
331
+ t.syncCode();
332
+ }
333
+
334
+ t.$editor.addClass(pfx + 'editor')
335
+ .attr('contenteditable', true)
336
+ .attr('dir', t.o.dir)
337
+ .html(html);
338
+
339
+ if(t.o.resetCss)
340
+ t.$editor.addClass(pfx + 'reset-css');
341
+
342
+ if(!t.o.autogrow){
343
+ $.each([t.$editor, t.$e], function(i, $el){
344
+ $el.css({
345
+ height: t.height,
346
+ overflow: 'auto'
347
+ });
348
+ });
349
+ }
350
+
351
+ if(t.o.semantic){
352
+ t.$editor.html(
353
+ t.$editor.html()
354
+ .replace('<br>', '</p><p>')
355
+ .replace('&nbsp;', '')
356
+ );
357
+ t.semanticCode();
358
+ }
359
+
360
+
361
+
362
+ t.$editor
363
+ .on('dblclick', 'img', function(){
364
+ var $img = $(this);
365
+ t.openModalInsert(t.lang.insertImage, {
366
+ url: {
367
+ label: 'URL',
368
+ value: $img.attr('src'),
369
+ required: true
370
+ },
371
+ alt: {
372
+ label: 'description',
373
+ value: $img.attr('alt')
374
+ }
375
+ }, function(v){
376
+ $img.attr('src', v.url);
377
+ $img.attr('alt', v.alt);
378
+ });
379
+ return false;
380
+ })
381
+ .on('keyup', function(e){
382
+ t.semanticCode(false, e.which === 13);
383
+ })
384
+ .on('blur', function(){
385
+ t.syncCode();
386
+ });
387
+ },
388
+
389
+
390
+ // Build the Textarea which contain HTML generated code
391
+ buildTextarea: function(){
392
+ return $('<textarea/>', {
393
+ name: this.$e.attr('id'),
394
+ height: this.height
395
+ });
396
+ },
397
+
398
+
399
+ // Build button pane, use o.btns and o.btnsAdd options
400
+ buildBtnPane: function(){
401
+ var t = this,
402
+ pfx = t.o.prefix;
403
+
404
+ if(t.o.btns === false)
405
+ return;
406
+
407
+ t.$btnPane = $('<ul/>', {
408
+ class: pfx + 'button-pane'
409
+ });
410
+
411
+ $.each(t.o.btns.concat(t.o.btnsAdd), function(i, btn){
412
+ // Managment of group of buttons
413
+ try {
414
+ var b = btn.split('btnGrp-');
415
+ if(b[1] !== undefined)
416
+ btn = $.trumbowyg.btnsGrps[b[1]];
417
+ } catch(e){}
418
+
419
+ if(!$.isArray(btn))
420
+ btn = [btn];
421
+
422
+ $.each(btn, function(i, b){
423
+ try { // Prevent buildBtn error
424
+ var $li = $('<li/>');
425
+
426
+ if(b === '|') // It's a separator
427
+ $li.addClass(pfx + 'separator');
428
+ else if(t.isSupportedBtn(b)) // It's a supported button
429
+ $li.append(t.buildBtn(b));
430
+
431
+ t.$btnPane.append($li);
432
+ } catch(e){}
433
+ });
434
+ });
435
+
436
+ // Build right li for fullscreen and close buttons
437
+ var $liRight = $('<li/>', {
438
+ class: pfx + 'not-disable ' + pfx + 'buttons-right'
439
+ });
440
+
441
+ // Add the fullscreen button
442
+ if(t.o.fullscreenable)
443
+ $liRight
444
+ .append(t.buildRightBtn('fullscreen')
445
+ .on('click', function(){
446
+ var cssClass = pfx + 'fullscreen';
447
+ t.$box.toggleClass(cssClass);
448
+
449
+ if(t.$box.hasClass(cssClass)){
450
+ $('body').css('overflow', 'hidden');
451
+ $.each([t.$editor, t.$e], function(){
452
+ $(this).css({
453
+ height: 'calc(100% - 35px)',
454
+ overflow: 'auto'
455
+ });
456
+ });
457
+ t.$btnPane.css('width', '100%');
458
+ } else {
459
+ $('body').css('overflow', 'auto');
460
+ t.$box.removeAttr('style');
461
+ if(!t.o.autogrow)
462
+ $([t.$editor, t.$e]).each(function(i, $el){
463
+ $el.css('height', t.height);
464
+ });
465
+ }
466
+ $(window).trigger('scroll');
467
+ }));
468
+
469
+ // Build and add close button
470
+ if(t.o.closable)
471
+ $liRight
472
+ .append(
473
+ t.buildRightBtn('close')
474
+ .on('click', function(){
475
+ if(t.$box.hasClass(pfx + 'fullscreen'))
476
+ $('body').css('overflow', 'auto');
477
+ t.destroy();
478
+ })
479
+ );
480
+
481
+
482
+ // Add right li only if isn't empty
483
+ if($liRight.not(':empty'))
484
+ t.$btnPane.append($liRight);
485
+
486
+ t.$box.prepend(t.$btnPane);
487
+ },
488
+
489
+
490
+ // Build a button and his action
491
+ buildBtn: function(n){ // n is name of the button
492
+ var t = this,
493
+ pfx = t.o.prefix,
494
+ btn = t.o.btnsDef[n],
495
+ d = btn.dropdown,
496
+ textDef = t.lang[n] || n,
497
+
498
+ $btn = $('<button/>', {
499
+ type: 'button',
500
+ class: pfx + n +'-button' + (btn.ico ? ' '+ pfx + btn.ico +'-button' : ''),
501
+ text: btn.text || btn.title || textDef,
502
+ title: btn.title || btn.text || textDef,
503
+ mousedown: function(e){
504
+ if(!d || t.$box.find('.'+n+'-'+pfx + 'dropdown').is(':hidden'))
505
+ $('body').trigger('mousedown');
506
+
507
+ if(t.$btnPane.hasClass(pfx + 'disable') && !$(this).hasClass(pfx + 'active') && !$(this).parent().hasClass(pfx + 'not-disable'))
508
+ return false;
509
+
510
+ t.execCmd((d ? 'dropdown' : false) || btn.func || n,
511
+ btn.param || n);
512
+
513
+ e.stopPropagation();
514
+ e.preventDefault();
515
+ }
516
+ });
517
+
518
+
519
+
520
+ if(d){
521
+ $btn.addClass(pfx + 'open-dropdown');
522
+ var c = pfx + 'dropdown',
523
+ dd = $('<div/>', { // the dropdown
524
+ class: n + '-' + c + ' ' + c + ' ' + pfx + 'fixed-top'
525
+ });
526
+ for(var i = 0, l = d.length; i < l; i++)
527
+ if(t.o.btnsDef[d[i]] && t.isSupportedBtn(d[i]))
528
+ dd.append(t.buildSubBtn(d[i]));
529
+ t.$box.append(dd.hide());
530
+ }
531
+
532
+ return $btn;
533
+ },
534
+ // Build a button for dropdown menu
535
+ // @param n : name of the subbutton
536
+ buildSubBtn: function(n){
537
+ var t = this,
538
+ btnDef = t.o.btnsDef[n];
539
+ return $('<button/>', {
540
+ type: 'button',
541
+ text: btnDef.text || btnDef.title || t.lang[n] || n,
542
+ mousedown: function(e){
543
+ $('body').trigger('mousedown');
544
+
545
+ t.execCmd(btnDef.func || n,
546
+ btnDef.param || n);
547
+
548
+ e.stopPropagation();
549
+ e.preventDefault();
550
+ return false;
551
+ }
552
+ });
553
+ },
554
+ // Build a button for right li
555
+ // @param n : name of the right button
556
+ buildRightBtn: function(n){
557
+ return $('<button/>', {
558
+ type: 'button',
559
+ class: this.o.prefix + n + '-button',
560
+ title: this.lang[n],
561
+ text: this.lang[n]
562
+ });
563
+ },
564
+ // Check if button is supported
565
+ isSupportedBtn: function(btn){
566
+ return typeof this.o.btnsDef[btn].isSupported !== 'function' || this.o.btnsDef[btn].isSupported();
567
+ },
568
+
569
+ // Build overlay for modal box
570
+ buildOverlay: function(){
571
+ var t = this;
572
+ t.$overlay = $('<div/>', {
573
+ class: t.o.prefix + 'overlay'
574
+ }).css({
575
+ top: t.$btnPane.outerHeight(),
576
+ height: (parseInt(t.$editor.outerHeight()) + 1) + 'px'
577
+ }).appendTo(t.$box);
578
+ return t.$overlay;
579
+ },
580
+ showOverlay: function(){
581
+ var t = this;
582
+ $(window).trigger('scroll');
583
+ t.$overlay.fadeIn(t.o.duration);
584
+ t.$box.addClass(t.o.prefix + 'box-blur');
585
+ },
586
+ hideOverlay: function(){
587
+ var t = this;
588
+ t.$overlay.fadeOut(t.o.duration/4);
589
+ t.$box.removeClass(t.o.prefix + 'box-blur');
590
+ },
591
+
592
+ // Management of fixed button pane
593
+ fixedBtnPaneEvents: function(){
594
+ var t = this,
595
+ ffw = t.o.fixedFullWidth;
596
+ if(!t.o.fixedBtnPane)
597
+ return;
598
+
599
+ t.isFixed = false;
600
+
601
+ $(window)
602
+ .on('scroll resize', function(){
603
+ if(!t.$box)
604
+ return;
605
+
606
+ t.syncCode();
607
+
608
+ var s = $(window).scrollTop(), // s is top scroll
609
+ o = t.$box.offset().top + 1, // o is offset
610
+ toFixed = (s - o > 0) && ((s - o - parseInt(t.height)) < 0),
611
+ bp = t.$btnPane,
612
+ mt = bp.css('height'),
613
+ oh = bp.outerHeight();
614
+
615
+ if(toFixed){
616
+ if(!t.isFixed){
617
+ t.isFixed = true;
618
+ bp.css({
619
+ position: 'fixed',
620
+ top: 0,
621
+ left: ffw ? '0' : 'auto',
622
+ zIndex: 7
623
+ });
624
+ t.$editor.css({ marginTop: mt });
625
+ t.$e.css({ marginTop: mt });
626
+ }
627
+ bp.css({
628
+ width: ffw ? '100%' : ((parseInt(t.$box.css('width'))-1) + 'px')
629
+ });
630
+
631
+ $('.' + t.o.prefix + 'fixed-top', t.$box).css({
632
+ position: ffw ? 'fixed' : 'absolute',
633
+ top: ffw ? oh : parseInt(oh) + (s - o) + 'px',
634
+ zIndex: 15
635
+ });
636
+ } else if(t.isFixed) {
637
+ t.isFixed = false;
638
+ bp.removeAttr('style');
639
+ t.$editor.css({ marginTop: 0 });
640
+ t.$e.css({ marginTop: 0 });
641
+ $('.' + t.o.prefix + 'fixed-top', t.$box).css({
642
+ position: 'absolute',
643
+ top: oh
644
+ });
645
+ }
646
+ });
647
+ },
648
+
649
+
650
+
651
+ // Destroy the editor
652
+ destroy: function(){
653
+ var t = this,
654
+ pfx = t.o.prefix,
655
+ h = t.height,
656
+ html = t.html();
657
+
658
+ if(t.isTextarea)
659
+ t.$box.after(
660
+ t.$e.css({height: h})
661
+ .val(html)
662
+ .removeClass(pfx + 'textarea')
663
+ .show()
664
+ );
665
+ else
666
+ t.$box.after(
667
+ t.$editor
668
+ .css({height: h})
669
+ .removeClass(pfx + 'editor')
670
+ .attr('contenteditable', false)
671
+ .html(html)
672
+ .show()
673
+ );
674
+
675
+ t.$box.remove();
676
+ t.$creator.removeData('trumbowyg');
677
+ },
678
+
679
+
680
+
681
+ // Empty the editor
682
+ empty: function(){
683
+ this.$e.val('');
684
+ this.syncCode(true);
685
+ },
686
+
687
+
688
+
689
+ // Function call when click on viewHTML button
690
+ toggle: function(){
691
+ var t = this,
692
+ pfx = t.o.prefix;
693
+ t.semanticCode(false, true);
694
+ t.$editor.toggle();
695
+ t.$e.toggle();
696
+ t.$btnPane.toggleClass(pfx + 'disable');
697
+ t.$btnPane.find('.'+pfx + 'viewHTML-button').toggleClass(pfx + 'active');
698
+ },
699
+
700
+ // Open dropdown when click on a button which open that
701
+ dropdown: function(name){
702
+ var t = this,
703
+ pfx = t.o.prefix,
704
+ $dropdown = t.$box.find('.'+name+'-'+pfx + 'dropdown'),
705
+ $btn = t.$btnPane.find('.'+pfx+name+'-button');
706
+
707
+ if($dropdown.is(':hidden')){
708
+ var o = $btn.offset().left;
709
+ $btn.addClass(pfx + 'active');
710
+
711
+ $dropdown.css({
712
+ position: 'absolute',
713
+ top: t.$btnPane.outerHeight(),
714
+ left: (t.o.fixedFullWidth && t.isFixed) ? o+'px' : (o - t.$btnPane.offset().left)+'px'
715
+ }).show();
716
+
717
+ $(window).trigger('scroll');
718
+
719
+ $('body').on('mousedown', function(){
720
+ $('.' + pfx + 'dropdown').hide();
721
+ $('.' + pfx + 'active').removeClass(pfx + 'active');
722
+ $('body').off('mousedown');
723
+ });
724
+ } else
725
+ $('body').trigger('mousedown');
726
+ },
727
+
728
+
729
+
730
+
731
+ // HTML Code management
732
+ html: function(html){
733
+ var t = this;
734
+ if(html){
735
+ t.$e.val(html);
736
+ t.syncCode(true);
737
+ return t;
738
+ } else
739
+ return t.$e.val();
740
+ },
741
+ syncCode: function(force){
742
+ var t = this;
743
+ if(!force && t.$editor.is(':visible'))
744
+ t.$e.val(t.$editor.html());
745
+ else
746
+ t.$editor.html(t.$e.val());
747
+
748
+ if(t.o.autogrow){
749
+ t.height = t.$editor.css('height');
750
+ t.$e.css({ height: t.height });
751
+ }
752
+ },
753
+
754
+ // Analyse and update to semantic code
755
+ // @param force : force to sync code from textarea
756
+ // @param full : wrap text nodes in <p>
757
+ semanticCode: function(force, full){
758
+ var t = this;
759
+ t.syncCode(force);
760
+
761
+ if(t.o.semantic){
762
+ t.semanticTag('b', 'strong');
763
+ t.semanticTag('i', 'em');
764
+ t.semanticTag('strike', 'del');
765
+
766
+ if(full){
767
+ // Wrap text nodes in p
768
+ t.$editor.contents()
769
+ .filter(function(){
770
+ // Only non-empty text nodes
771
+ return this.nodeType === 3 && $.trim(this.nodeValue).length > 0;
772
+ }).wrap('<p></p>').end()
773
+
774
+ // Remove all br
775
+ .filter('br').remove();
776
+
777
+ t.saveSelection();
778
+ t.semanticTag('div', 'p');
779
+ t.restoreSelection();
780
+ }
781
+
782
+ t.$e.val(t.$editor.html());
783
+ }
784
+ },
785
+ semanticTag: function(oldTag, newTag){
786
+ $(oldTag, this.$editor).each(function(){
787
+ $(this).replaceWith(function(){
788
+ return '<'+newTag+'>' + $(this).html() + '</'+newTag+'>';
789
+ });
790
+ });
791
+ },
792
+
793
+
794
+ // Function call when user click on "Insert Link"
795
+ createLink: function(){
796
+ var t = this;
797
+ t.saveSelection();
798
+ t.openModalInsert(t.lang.createLink, {
799
+ url: {
800
+ label: 'URL',
801
+ value: 'http://',
802
+ required: true
803
+ },
804
+ title: {
805
+ label: t.lang.title,
806
+ value: t.selection
807
+ },
808
+ text: {
809
+ label: t.lang.text,
810
+ value: t.selection
811
+ }
812
+ }, function(v){ // v is value
813
+ t.execCmd('createLink', v.url);
814
+ var l = $('a[href="'+v.url+'"]:not([title])', t.$box);
815
+ if(v.text.length > 0)
816
+ l.text(v.text);
817
+
818
+ if(v.title.length > 0)
819
+ l.attr('title', v.title);
820
+
821
+ return true;
822
+ });
823
+ },
824
+ insertImage: function(){
825
+ var t = this;
826
+ t.saveSelection();
827
+ t.openModalInsert(t.lang.insertImage, {
828
+ url: {
829
+ label: 'URL',
830
+ value: 'http://',
831
+ required: true
832
+ },
833
+ alt: {
834
+ label: t.lang.description,
835
+ value: t.selection
836
+ }
837
+ }, function(v){ // v are values
838
+ t.execCmd('insertImage', v.url);
839
+ $('img[src="'+v.url+'"]:not([alt])', t.$box).attr('alt', v.alt);
840
+ return true;
841
+ });
842
+ },
843
+
844
+
845
+ /*
846
+ * Call method of trumbowyg if exist
847
+ * else try to call anonymous function
848
+ * and finaly native execCommand
849
+ */
850
+ execCmd: function(cmd, param){
851
+ var t = this;
852
+ if(cmd != 'dropdown')
853
+ t.$editor.focus();
854
+
855
+ try {
856
+ t[cmd](param);
857
+ } catch(e){
858
+ try {
859
+ cmd(param, t);
860
+ } catch(e2){
861
+ t.$editor.focus();
862
+ if(cmd == 'insertHorizontalRule')
863
+ param = null;
864
+ else if(cmd == 'formatBlock' && (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0))
865
+ param = '<' + param + '>';
866
+
867
+ document.execCommand(cmd, false, param);
868
+ }
869
+ }
870
+ t.syncCode();
871
+ },
872
+
873
+
874
+ // Open a modal box
875
+ openModal: function(title, content){
876
+ var t = this,
877
+ pfx = t.o.prefix;
878
+
879
+ // No open a modal box when exist other modal box
880
+ if($('.' + pfx + 'modal-box', t.$box).size() > 0)
881
+ return false;
882
+
883
+ t.saveSelection();
884
+ t.showOverlay();
885
+
886
+ // Disable all btnPane btns
887
+ t.$btnPane.addClass(pfx + 'disable');
888
+
889
+ // Build out of ModalBox, it's the mask for animations
890
+ var $modal = $('<div/>', {
891
+ class: pfx + 'modal ' + pfx + 'fixed-top'
892
+ }).css({
893
+ top: (parseInt(t.$btnPane.css('height')) + 1) + 'px'
894
+ }).appendTo(t.$box);
895
+
896
+ // Click on overflay close modal by cancelling them
897
+ t.$overlay.one('click', function(e){
898
+ e.preventDefault();
899
+ $modal.trigger(pfx + 'cancel');
900
+ });
901
+
902
+ // Build the form
903
+ var $form = $('<form/>', {
904
+ action: '',
905
+ html: content
906
+ })
907
+ .on('submit', function(e){
908
+ e.preventDefault();
909
+ $modal.trigger(pfx + 'confirm');
910
+ })
911
+ .on('reset', function(e){
912
+ e.preventDefault();
913
+ $modal.trigger(pfx + 'cancel');
914
+ });
915
+
916
+
917
+ // Build ModalBox and animate to show them
918
+ var $box = $('<div/>', {
919
+ class: pfx + 'modal-box',
920
+ html: $form
921
+ })
922
+ .css({
923
+ top: '-' + parseInt(t.$btnPane.outerHeight()) + 'px',
924
+ opacity: 0
925
+ })
926
+ .appendTo($modal)
927
+ .animate({
928
+ top: 0,
929
+ opacity: 1
930
+ }, t.o.duration / 2);
931
+
932
+
933
+ // Append title
934
+ $('<span/>', {
935
+ text: title,
936
+ class: pfx + 'modal-title'
937
+ }).prependTo($box);
938
+
939
+
940
+ // Focus in modal box
941
+ $box.find('input:first').focus();
942
+
943
+
944
+ // Append Confirm and Cancel buttons
945
+ t.buildModalBtn('submit', $box);
946
+ t.buildModalBtn('reset', $box);
947
+
948
+
949
+ $(window).trigger('scroll');
950
+
951
+ return $modal;
952
+ },
953
+ // @param n is name of modal
954
+ buildModalBtn: function(n, modal){
955
+ var t = this,
956
+ pfx = t.o.prefix;
957
+
958
+ return $('<button/>', {
959
+ class: pfx + 'modal-button ' + pfx + 'modal-' + n,
960
+ type: n,
961
+ text: t.lang[n] || n
962
+ }).appendTo(modal.find('form'));
963
+ },
964
+ // close current modal box
965
+ closeModal: function(){
966
+ var t = this,
967
+ pfx = t.o.prefix;
968
+
969
+ t.$btnPane.removeClass(pfx + 'disable');
970
+ t.$overlay.off();
971
+
972
+ var $modalBox = $('.' + pfx + 'modal-box', t.$box);
973
+
974
+ $modalBox.animate({
975
+ top: '-' + $modalBox.css('height')
976
+ }, t.o.duration/2, function(){
977
+ $(this).parent().remove();
978
+ t.hideOverlay();
979
+ });
980
+ },
981
+ // Preformated build and management modal
982
+ openModalInsert: function(title, fields, cmd){
983
+ var t = this,
984
+ pfx = t.o.prefix,
985
+ lg = t.lang,
986
+ html = '';
987
+
988
+ for(var f in fields){
989
+ var fd = fields[f], // field definition
990
+ label = (fd.label === undefined) ? (lg[f] ? lg[f] : f) : (lg[fd.label] ? lg[fd.label] : fd.label);
991
+
992
+ if(fd.name === undefined)
993
+ fd.name = f;
994
+
995
+ if(!fd.pattern && f === 'url'){
996
+ fd.pattern = /^(http|https):\/\/([\w~#!:.?+=&%@!\-\/]+)$/;
997
+ fd.patternError = lg.invalidUrl;
998
+ }
999
+
1000
+ html += '<label><input type="'+(fd.type || 'text')+'" name="'+fd.name+'" value="'+(fd.value || '')+'"><span class="'+pfx+'input-infos"><span>'+label+'</span></span></label>';
1001
+ }
1002
+
1003
+ return t.openModal(title, html)
1004
+ .on(pfx + 'confirm', function(){
1005
+ var $form = $(this).find('form'),
1006
+ valid = true,
1007
+ v = {}; // values
1008
+
1009
+ for(var f in fields){
1010
+ var $field = $('input[name="'+f+'"]', $form);
1011
+
1012
+ v[f] = $.trim($field.val());
1013
+
1014
+ // Validate value
1015
+ if(fields[f].required && v[f] === ''){
1016
+ valid = false;
1017
+ t.addErrorOnModalField($field, t.lang.required);
1018
+ } else if(fields[f].pattern && !fields[f].pattern.test(v[f])){
1019
+ valid = false;
1020
+ t.addErrorOnModalField($field, fields[f].patternError);
1021
+ }
1022
+ }
1023
+
1024
+ if(valid){
1025
+ t.restoreSelection();
1026
+
1027
+ if(cmd(v, fields)){
1028
+ t.syncCode();
1029
+ t.closeModal();
1030
+ $(this).off(pfx + 'confirm');
1031
+ }
1032
+ }
1033
+ })
1034
+ .one(pfx + 'cancel', function(){
1035
+ $(this).off(pfx + 'confirm');
1036
+ t.closeModal();
1037
+ t.restoreSelection();
1038
+ });
1039
+ },
1040
+ addErrorOnModalField: function($field, err){
1041
+ var pfx = this.o.prefix,
1042
+ $label = $field.parent();
1043
+
1044
+ $field.on('change keyup', function(){
1045
+ $label.removeClass(pfx + 'input-error');
1046
+ });
1047
+ $label
1048
+ .addClass(pfx + 'input-error')
1049
+ .find('input+span').append(
1050
+ $('<span/>', {
1051
+ class: pfx +'msg-error',
1052
+ text: err
1053
+ })
1054
+ );
1055
+ },
1056
+
1057
+
1058
+
1059
+
1060
+ // Selection management
1061
+ saveSelection: function(){
1062
+ var t = this,
1063
+ d = document;
1064
+
1065
+ t.selection = null;
1066
+ if(window.getSelection){
1067
+ var s = window.getSelection();
1068
+ if(s.getRangeAt && s.rangeCount)
1069
+ t.selection = s.getRangeAt(0);
1070
+ } else if(d.selection && d.selection.createRange)
1071
+ t.selection = d.selection.createRange();
1072
+ },
1073
+ restoreSelection: function(){
1074
+ var range = this.selection;
1075
+ if(range){
1076
+ if(window.getSelection){
1077
+ var s = window.getSelection();
1078
+ s.removeAllRanges();
1079
+ s.addRange(range);
1080
+ } else if(document.selection && range.select)
1081
+ range.select();
1082
+ }
1083
+ },
1084
+
1085
+
1086
+
1087
+ // Return true if must enable Trumbowyg on this mobile device
1088
+ isEnabled: function(){
1089
+ var exprTablet = new RegExp("(iPad|webOS)"),
1090
+ exprMobile = new RegExp("(iPhone|iPod|Android|BlackBerry|Windows Phone|ZuneWP7)"),
1091
+ ua = navigator.userAgent;
1092
+
1093
+ return (this.o.tablet === true && exprTablet.test(ua)) || (this.o.mobile === true && exprMobile.test(ua));
1094
+ }
1095
+ };
1096
+ })(window, document, jQuery);