torba 0.5.1 → 0.6.0

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