torba 0.5.1 → 0.6.0

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.
@@ -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);