trumbowyg_rails 1.1.2

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 (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);