PageSourceSearch

https://www.streamforex.ng/bitrix/templates/sf_v_3/js/wysibb/jquery.wysibb.js

js streamforex.ng collected 2026-10-06 06:38:56 UTC 144,945 bytes, 3,247 lines download raw bytes

1/*! WysiBB v1.5.1 2014-03-26 
2 Author: Vadim Dobroskok
3 */
4if (typeof (WBBLANG) == "undefined") {
5    WBBLANG = {};
6}
7WBBLANG['en'] = CURLANG = {
8    bold: "Bold",
9    italic: "Italic",
10    underline: "Underline",
11    strike: "Strike",
12    link: "Link",
13    img: "Insert image",
14    sup: "Superscript",
15    sub: "Subscript",
16    justifyleft: "Align left",
17    justifycenter: "Align center",
18    justifyright: "Align right",
19    table: "Insert table",
20    bullist: "• Unordered list",
21    numlist: "1. Ordered list",
22    quote: "Quote",
23    offtop: "Offtop",
24    code: "Code",
25    spoiler: "Spoiler",
26    fontcolor: "Font color",
27    fontsize: "Font size",
28    fontfamily: "Font family",
29    fs_verysmall: "Very small",
30    fs_small: "Small",
31    fs_normal: "Normal",
32    fs_big: "Big",
33    fs_verybig: "Very big",
34    smilebox: "Insert emoticon",
35    video: "Insert YouTube",
36    removeFormat: "Remove Format",
37
38    modal_link_title: "Insert link",
39    modal_link_text: "Display text",
40    modal_link_url: "URL",
41    modal_email_text: "Display email",
42    modal_email_url: "Email",
43    modal_link_tab1: "Insert URL",
44
45    modal_img_title: "Insert image",
46    modal_img_tab1: "Insert URL",
47    modal_img_tab2: "Upload image",
48    modal_imgsrc_text: "Enter image URL",
49    modal_img_btn: "Choose file",
50    add_attach: "Add Attachment",
51
52    modal_video_text: "Enter the URL of the video",
53
54    close: "Close",
55    save: "Save",
56    cancel: "Cancel",
57    remove: "Delete",
58
59    validation_err: "The entered data is invalid",
60    error_onupload: "Error during file upload",
61
62    fileupload_text1: "Drop file here",
63    fileupload_text2: "or",
64
65    loading: "Loading",
66    auto: "Auto",
67    views: "Views",
68    downloads: "Downloads",
69
70    //smiles
71    sm1: "Smile",
72    sm2: "Laughter",
73    sm3: "Wink",
74    sm4: "Thank you",
75    sm5: "Scold",
76    sm6: "Shock",
77    sm7: "Angry",
78    sm8: "Pain",
79    sm9: "Sick"
80};
81wbbdebug = true;
82(function ($) {
83    'use strict';
84    $.wysibb = function (txtArea, settings) {
85        $(txtArea).data("wbb", this);
86
87        if (settings && settings.deflang && typeof(WBBLANG[settings.deflang]) != "undefined") {
88            CURLANG = WBBLANG[settings.deflang];
89        }
90        if (settings && settings.lang && typeof(WBBLANG[settings.lang]) != "undefined") {
91            CURLANG = WBBLANG[settings.lang];
92        }
93        this.txtArea = txtArea;
94        this.$txtArea = $(txtArea);
95        var id = this.$txtArea.attr("id") || this.setUID(this.txtArea);
96        this.options = {
97            bbmode: false,
98            onlyBBmode: false,
99            themeName: "default",
100            bodyClass: "",
101            lang: "ru",
102            tabInsert: true,
103//			toolbar:			false,
104            //img upload config 
105            imgupload: false,
106            img_uploadurl: "/iupload.php",
107            img_maxwidth: 800,
108            img_maxheight: 800,
109            hotkeys: true,
110            showHotkeys: true,
111            autoresize: true,
112            resize_maxheight: 800,
113            loadPageStyles: true,
114            traceTextarea: true,
115//			direction:			"ltr",
116            smileConversion: true,
117
118            //END img upload config 
119            buttons: "bold,italic,underline,strike,sup,sub,|,img,video,link,|,bullist,numlist,|,fontcolor,fontsize,fontfamily,|,justifyleft,justifycenter,justifyright,|,quote,code,table,removeFormat",
120            allButtons: {
121                bold: {
122                    title: CURLANG.bold,
123                    buttonHTML: '<span class="fonticon ve-tlb-bold1">\uE018</span>',
124                    excmd: 'bold',
125                    hotkey: 'ctrl+b',
126                    transform: {
127                        '<b>{SELTEXT}</b>': "[b]{SELTEXT}[/b]",
128                        '<strong>{SELTEXT}</strong>': "[b]{SELTEXT}[/b]"
129                    }
130                },
131                italic: {
132                    title: CURLANG.italic,
133                    buttonHTML: '<span class="fonticon ve-tlb-italic1">\uE001</span>',
134                    excmd: 'italic',
135                    hotkey: 'ctrl+i',
136                    transform: {
137                        '<i>{SELTEXT}</i>': "[i]{SELTEXT}[/i]",
138                        '<em>{SELTEXT}</em>': "[i]{SELTEXT}[/i]"
139                    }
140                },
141                underline: {
142                    title: CURLANG.underline,
143                    buttonHTML: '<span class="fonticon ve-tlb-underline1">\uE002</span>',
144                    excmd: 'underline',
145                    hotkey: 'ctrl+u',
146                    transform: {
147                        '<u>{SELTEXT}</u>': "[u]{SELTEXT}[/u]"
148                    }
149                },
150                strike: {
151                    title: CURLANG.strike,
152                    buttonHTML: '<span class="fonticon fi-stroke1 ve-tlb-strike1">\uE003</span>',
153                    excmd: 'strikeThrough',
154                    transform: {
155                        '<strike>{SELTEXT}</strike>': "[s]{SELTEXT}[/s]",
156                        '<s>{SELTEXT}</s>': "[s]{SELTEXT}[/s]"
157                    }
158                },
159                sup: {
160                    title: CURLANG.sup,
161                    buttonHTML: '<span class="fonticon ve-tlb-sup1">\uE005</span>',
162                    excmd: 'superscript',
163                    transform: {
164                        '<sup>{SELTEXT}</sup>': "[sup]{SELTEXT}[/sup]"
165                    }
166                },
167                sub: {
168                    title: CURLANG.sub,
169                    buttonHTML: '<span class="fonticon ve-tlb-sub1">\uE004</span>',
170                    excmd: 'subscript',
171                    transform: {
172                        '<sub>{SELTEXT}</sub>': "[sub]{SELTEXT}[/sub]"
173                    }
174                },
175                link: {
176                    title: CURLANG.link,
177                    buttonHTML: '<span class="fonticon ve-tlb-link1">\uE007</span>',
178                    hotkey: 'ctrl+shift+2',
179                    modal: {
180                        title: CURLANG.modal_link_title,
181                        width: "500px",
182                        tabs: [
183                            {
184                                input: [
185                                    {param: "SELTEXT", title: CURLANG.modal_link_text, type: "div"},
186                                    {param: "URL", title: CURLANG.modal_link_url, validation: '^http(s)?://'}
187                                ]
188                            }
189                        ]
190                    },
191                    transform: {
192                        '<a href="{URL}">{SELTEXT}</a>': "[url={URL}]{SELTEXT}[/url]",
193                        '<a href="{URL}">{URL}</a>': "[url]{URL}[/url]"
194                    }
195                },
196                img: {
197                    title: CURLANG.img,
198                    buttonHTML: '<span class="fonticon ve-tlb-img1">\uE006</span>',
199                    hotkey: 'ctrl+shift+1',
200                    addWrap: true,
201                    modal: {
202                        title: CURLANG.modal_img_title,
203                        width: "600px",
204                        tabs: [
205                            {
206                                title: CURLANG.modal_img_tab1,
207                                input: [
208                                    {param: "SRC", title: CURLANG.modal_imgsrc_text, validation: '^http(s)?://.*?\.(jpg|png|gif|jpeg)$'}
209                                ]
210                            }
211                        ],
212                        onLoad: this.imgLoadModal
213                    },
214                    transform: {
215                        '<img src="{SRC}" />': "[img]{SRC}[/img]",
216                        '<img src="{SRC}" width="{WIDTH}" height="{HEIGHT}"/>': "[img width={WIDTH},height={HEIGHT}]{SRC}[/img]"
217                    }
218                },
219                bullist: {
220                    title: CURLANG.bullist,
221                    buttonHTML: '<span class="fonticon ve-tlb-list1">\uE009</span>',
222                    excmd: 'insertUnorderedList',
223                    transform: {
224                        '<ul>{SELTEXT}</ul>': "[list]{SELTEXT}[/list]",
225                        '<li>{SELTEXT}</li>': "[*]{SELTEXT}[/*]"
226                    }
227                },
228                numlist: {
229                    title: CURLANG.numlist,
230                    buttonHTML: '<span class="fonticon ve-tlb-numlist1">\uE00a</span>',
231                    excmd: 'insertOrderedList',
232                    transform: {
233                        '<ol>{SELTEXT}</ol>': "[list=1]{SELTEXT}[/list]",
234                        '<li>{SELTEXT}</li>': "[*]{SELTEXT}[/*]"
235                    }
236                },
237                quote: {
238                    title: CURLANG.quote,
239                    buttonHTML: '<span class="fonticon ve-tlb-quote1">\uE00c</span>',
240                    hotkey: 'ctrl+shift+3',
241                    //subInsert: true,
242                    transform: {
243                        '<blockquote>{SELTEXT}</blockquote>': "[quote]{SELTEXT}[/quote]"
244                    }
245                },
246                code: {
247                    title: CURLANG.code,
248                    buttonText: '[code]',
249                    /* buttonHTML: '<span class="fonticon">\uE00d</span>', */
250                    hotkey: 'ctrl+shift+4',
251                    onlyClearText: true,
252                    transform: {
253                        '<code>{SELTEXT}</code>': "[code]{SELTEXT}[/code]"
254                    }
255                },
256                offtop: {
257                    title: CURLANG.offtop,
258                    buttonText: 'offtop',
259                    transform: {
260                        '<span style="font-size:10px;color:#ccc">{SELTEXT}</span>': "[offtop]{SELTEXT}[/offtop]"
261                    }
262                },
263                fontcolor: {
264                    type: "colorpicker",
265                    title: CURLANG.fontcolor,
266                    excmd: "foreColor",
267                    valueBBname: "color",
268                    subInsert: true,
269                    colors: "#000000,#444444,#666666,#999999,#b6b6b6,#cccccc,#d8d8d8,#efefef,#f4f4f4,#ffffff,-, \
270							 #ff0000,#980000,#ff7700,#ffff00,#00ff00,#00ffff,#1e84cc,#0000ff,#9900ff,#ff00ff,-, \
271							 #f4cccc,#dbb0a7,#fce5cd,#fff2cc,#d9ead3,#d0e0e3,#c9daf8,#cfe2f3,#d9d2e9,#ead1dc, \
272							 #ea9999,#dd7e6b,#f9cb9c,#ffe599,#b6d7a8,#a2c4c9,#a4c2f4,#9fc5e8,#b4a7d6,#d5a6bd, \
273							 #e06666,#cc4125,#f6b26b,#ffd966,#93c47d,#76a5af,#6d9eeb,#6fa8dc,#8e7cc3,#c27ba0, \
274							 #cc0000,#a61c00,#e69138,#f1c232,#6aa84f,#45818e,#3c78d8,#3d85c6,#674ea7,#a64d79, \
275							 #900000,#85200C,#B45F06,#BF9000,#38761D,#134F5C,#1155Cc,#0B5394,#351C75,#741B47, \
276							 #660000,#5B0F00,#783F04,#7F6000,#274E13,#0C343D,#1C4587,#073763,#20124D,#4C1130",
277                    transform: {
278                        '<font color="{COLOR}">{SELTEXT}</font>': '[color={COLOR}]{SELTEXT}[/color]'
279                    }
280                },
281                table: {
282                    type: "table",
283                    title: CURLANG.table,
284                    cols: 10,
285                    rows: 10,
286                    cellwidth: 20,
287                    transform: {
288                        '<td>{SELTEXT}</td>': '[td]{SELTEXT}[/td]',
289                        '<tr>{SELTEXT}</tr>': '[tr]{SELTEXT}[/tr]',
290                        '<table class="wbb-table">{SELTEXT}</table>': '[table]{SELTEXT}[/table]'
291                    },
292                    skipRules: true
293                },
294                fontsize: {
295                    type: 'select',
296                    title: CURLANG.fontsize,
297                    options: "fs_verysmall,fs_small,fs_normal,fs_big,fs_verybig"
298                },
299                fontfamily: {
300                    type: 'select',
301                    title: CURLANG.fontfamily,
302                    excmd: 'fontName',
303                    valueBBname: "font",
304                    options: [
305                        {title: "Arial", exvalue: "Arial"},
306                        {title: "Comic Sans MS", exvalue: "Comic Sans MS"},
307                        {title: "Courier New", exvalue: "Courier New"},
308                        {title: "Georgia", exvalue: "Georgia"},
309                        {title: "Lucida Sans Unicode", exvalue: "Lucida Sans Unicode"},
310                        {title: "Tahoma", exvalue: "Tahoma"},
311                        {title: "Times New Roman", exvalue: "Times New Roman"},
312                        {title: "Trebuchet MS", exvalue: "Trebuchet MS"},
313                        {title: "Verdana", exvalue: "Verdana"}
314                    ],
315                    transform: {
316                        '<font face="{FONT}">{SELTEXT}</font>': '[font={FONT}]{SELTEXT}[/font]'
317                    }
318                },
319                smilebox: {
320                    type: 'smilebox',
321                    title: CURLANG.smilebox,
322                    buttonHTML: '<span class="fonticon ve-tlb-smilebox1">\uE00b</span>'
323                },
324                justifyleft: {
325                    title: CURLANG.justifyleft,
326                    buttonHTML: '<span class="fonticon ve-tlb-textleft1">\uE015</span>',
327                    groupkey: 'align',
328                    transform: {
329                        '<p style="text-align:left">{SELTEXT}</p>': '[left]{SELTEXT}[/left]'
330                    }
331                },
332                justifyright: {
333                    title: CURLANG.justifyright,
334                    buttonHTML: '<span class="fonticon ve-tlb-textright1">\uE016</span>',
335                    groupkey: 'align',
336                    transform: {
337                        '<p style="text-align:right">{SELTEXT}</p>': '[right]{SELTEXT}[/right]'
338                    }
339                },
340                justifycenter: {
341                    title: CURLANG.justifycenter,
342                    buttonHTML: '<span class="fonticon ve-tlb-textcenter1">\uE014</span>',
343                    groupkey: 'align',
344                    transform: {
345                        '<p style="text-align:center">{SELTEXT}</p>': '[center]{SELTEXT}[/center]'
346                    }
347                },
348                video: {
349                    title: CURLANG.video,
350                    buttonHTML: '<span class="fonticon ve-tlb-video1">\uE008</span>',
351                    modal: {
352                        title: CURLANG.video,
353                        width: "600px",
354                        tabs: [
355                            {
356                                title: CURLANG.video,
357                                input: [
358                                    {param: "SRC", title: CURLANG.modal_video_text}
359                                ]
360                            }
361                        ],
362                        onSubmit: function (cmd, opt, queryState) {
363                            var url = this.$modal.find('input[name="SRC"]').val();
364                            if (url) {
365                                url = url.replace(/^\s+/, "").replace(/\s+$/, "");
366                            }
367                            var a;
368                            if (url.indexOf("youtu.be") != -1) {
369                                a = url.match(/^http[s]*:\/\/youtu\.be\/([a-z0-9_-]+)/i);
370                            } else {
371                                a = url.match(/^http[s]*:\/\/www\.youtube\.com\/watch\?.*?v=([a-z0-9_-]+)/i);
372                            }
373                            if (a && a.length == 2) {
374                                var code = a[1];
375                                this.insertAtCursor(this.getCodeByCommand(cmd, {src: code}));
376                            }
377                            this.closeModal();
378                            this.updateUI();
379                            return false;
380                        }
381                    },
382                    transform: {
383                        '<iframe src="http://www.youtube.com/embed/{SRC}" width="640" height="480" frameborder="0"></iframe>': '[video]{SRC}[/video]'
384                    }
385                },
386
387                //select options
388                fs_verysmall: {
389                    title: CURLANG.fs_verysmall,
390                    buttonText: "fs1",
391                    excmd: 'fontSize',
392                    exvalue: "1",
393                    transform: {
394                        '<font size="1">{SELTEXT}</font>': '[size=50]{SELTEXT}[/size]'
395                    }
396                },
397                fs_small: {
398                    title: CURLANG.fs_small,
399                    buttonText: "fs2",
400                    excmd: 'fontSize',
401                    exvalue: "2",
402                    transform: {
403                        '<font size="2">{SELTEXT}</font>': '[size=85]{SELTEXT}[/size]'
404                    }
405                },
406                fs_normal: {
407                    title: CURLANG.fs_normal,
408                    buttonText: "fs3",
409                    excmd: 'fontSize',
410                    exvalue: "3",
411                    transform: {
412                        '<font size="3">{SELTEXT}</font>': '[size=100]{SELTEXT}[/size]'
413                    }
414                },
415                fs_big: {
416                    title: CURLANG.fs_big,
417                    buttonText: "fs4",
418                    excmd: 'fontSize',
419                    exvalue: "4",
420                    transform: {
421                        '<font size="4">{SELTEXT}</font>': '[size=150]{SELTEXT}[/size]'
422                    }
423                },
424                fs_verybig: {
425                    title: CURLANG.fs_verybig,
426                    buttonText: "fs5",
427                    excmd: 'fontSize',
428                    exvalue: "6",
429                    transform: {
430                        '<font size="6">{SELTEXT}</font>': '[size=200]{SELTEXT}[/size]'
431                    }
432                },
433
434                removeformat: {
435                    title: CURLANG.removeFormat,
436                    buttonHTML: '<span class="fonticon ve-tlb-removeformat1">\uE00f</span>',
437                    excmd: "removeFormat"
438                }
439            },
440            systr: {
441                '<br/>': "\n",
442                '<span class="wbbtab">{SELTEXT}</span>': '   {SELTEXT}'
443            },
444            customRules: {
445                td: [
446                    ["[td]{SELTEXT}[/td]", {seltext: {rgx: false, attr: false, sel: false}}]
447                ],
448                tr: [
449                    ["[tr]{SELTEXT}[/tr]", {seltext: {rgx: false, attr: false, sel: false}}]
450                ],
451                table: [
452                    ["[table]{SELTEXT}[/table]", {seltext: {rgx: false, attr: false, sel: false}}]
453                ]
454                //blockquote: [["   {SELTEXT}",{seltext: {rgx:false,attr:false,sel:false}}]]
455            },
456            smileList: [
457                //{title:CURLANG.sm1, img: '<img src="{themePrefix}{themeName}/img/smiles/sm1.png" class="sm">', bbcode:":)"},
458            ],
459            attrWrap: ['src', 'color', 'href'] //use becouse FF and IE change values for this attr, modify [attr] to _[attr]
460        };
461
462        //FIX for Opera. Wait while iframe loaded
463        this.inited = this.options.onlyBBmode;
464
465        //init css prefix, if not set
466        if (!this.options.themePrefix) {
467            $('link').each($.proxy(function (idx, el) {
468                var sriptMatch = $(el).get(0).href.match(/(.*\/)(.*)\/wbbtheme\.css.*$/);
469                if (sriptMatch !== null) {
470                    this.options.themeName = sriptMatch[2];
471                    this.options.themePrefix = sriptMatch[1];
472                }
473            }, this));
474        }
475
476        //check for preset
477        if (typeof(WBBPRESET) != "undefined") {
478            if (WBBPRESET.allButtons) {
479                //clear transform
480                $.each(WBBPRESET.allButtons, $.proxy(function (k, v) {
481                    if (v.transform && this.options.allButtons[k]) {
482                        delete this.options.allButtons[k].transform;
483                    }
484                }, this));
485            }
486            $.extend(true, this.options, WBBPRESET);
487        }
488
489        if (settings && settings.allButtons) {
490            $.each(settings.allButtons, $.proxy(function (k, v) {
491                if (v.transform && this.options.allButtons[k]) {
492                    delete this.options.allButtons[k].transform;
493                }
494            }, this));
495        }
496        $.extend(true, this.options, settings);
497        this.init();
498    };
499
500    $.wysibb.prototype = {
501        lastid: 1,
502        init: function () {
503            $.log("Init", this);
504            //check for mobile
505            this.isMobile = function (a) {
506                (/android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a))
507            }(navigator.userAgent || navigator.vendor || window.opera);
508
509            //use bbmode on mobile devices
510            //this.isMobile = true; //TEMP
511            if (this.options.onlyBBmode === true) {
512                this.options.bbmode = true;
513            }
514            //create array of controls, for queryState
515            this.controllers = [];
516
517            //convert button string to array
518            this.options.buttons = this.options.buttons.toLowerCase();
519            this.options.buttons = this.options.buttons.split(",");
520
521            //init system transforms
522            this.options.allButtons["_systr"] = {};
523            this.options.allButtons["_systr"]["transform"] = this.options.systr;
524
525            this.smileFind();
526            this.initTransforms();
527            this.build();
528            this.initModal();
529            if (this.options.hotkeys === true && !this.isMobile) {
530                this.initHotkeys();
531            }
532
533            //sort smiles
534            if (this.options.smileList && this.options.smileList.length > 0) {
535                this.options.smileList.sort(function (a, b) {
536                    return (b.bbcode.length - a.bbcode.length);
537                })
538            }
539
540            this.$txtArea.parents("form").bind("submit", $.proxy(function () {
541                this.sync();
542                return true;
543            }, this));
544
545
546            //phpbb2
547            this.$txtArea.parents("form").find("input[id*='preview'],input[id*='submit'],input[class*='preview'],input[class*='submit'],input[name*='preview'],input[name*='submit']").bind("mousedown", $.proxy(function () {
548                this.sync();
549                setTimeout($.proxy(function () {
550                    if (this.options.bbmode === false) {
551                        this.$txtArea.removeAttr("wbbsync").val("");
552                    }
553                }, this), 1000);
554            }, this));
555            //end phpbb2
556
557            if (this.options.initCallback) {
558                this.options.initCallback.call(this);
559            }
560
561            $.log(this);
562
563        },
564        initTransforms: function () {
565            $.log("Create rules for transform HTML=>BB");
566            var o = this.options;
567            //need to check for active buttons
568            if (!o.rules) {
569                o.rules = {};
570            }
571            if (!o.groups) {
572                o.groups = {};
573            } //use for groupkey, For example: justifyleft,justifyright,justifycenter. It is must replace each other.
574            var btnlist = o.buttons.slice();
575
576            //add system transform
577            btnlist.push("_systr");
578            for (var bidx = 0; bidx < btnlist.length; bidx++) {
579                var ob = o.allButtons[btnlist[bidx]];
580                if (!ob) {
581                    continue;
582                }
583                ob.en = true;
584
585                //check for simplebbcode
586                if (ob.simplebbcode && $.isArray(ob.simplebbcode) && ob.simplebbcode.length == 2) {
587                    ob.bbcode = ob.html = ob.simplebbcode[0] + "{SELTEXT}" + ob.simplebbcode[1];
588                    if (ob.transform) delete ob.transform;
589                    if (ob.modal)  delete ob.modal;
590                }
591
592                //add transforms to option list
593                if (ob.type == "select" && typeof(ob.options) == "string") {
594                    var olist = ob.options.split(",");
595                    $.each(olist, function (i, op) {
596                        if ($.inArray(op, btnlist) == -1) {
597                            btnlist.push(op);
598                        }
599                    });
600                }
601                if (ob.transform && ob.skipRules !== true) {
602                    var obtr = $.extend({}, ob.transform);
603
604                    /* if (ob.addWrap) {
605                     //addWrap
606                     $.log("needWrap");
607                     for (var bhtml in obtr) {
608                     var bbcode = ob.transform[bhtml];
609                     var newhtml = '<span wbb="'+btnlist[bidx]+'">'+bhtml+'</span>';
610                     obtr[newhtml] = bbcode;
611                     }
612                     } */
613
614                    for (var bhtml in obtr) {
615                        var orightml = bhtml;
616                        var bbcode = obtr[bhtml];
617
618                        //create root selector for isContain bbmode
619                        if (!ob.bbSelector) {
620                            ob.bbSelector = [];
621                        }
622                        if ($.inArray(bbcode, ob.bbSelector) == -1) {
623                            ob.bbSelector.push(bbcode);
624                        }
625                        if (this.options.onlyBBmode === false) {
626
627                            //wrap attributes 
628                            bhtml = this.wrapAttrs(bhtml);
629
630
631                            var $bel = $(document.createElement('DIV')).append($(this.elFromString(bhtml, document)));
632                            var rootSelector = this.filterByNode($bel.children());
633
634
635                            //check if current rootSelector is exist, create unique selector for each transform (1.2.2)
636                            if (rootSelector == "div" || typeof(o.rules[rootSelector]) != "undefined") {
637                                //create unique selector
638                                $.log("create unique selector: " + rootSelector);
639                                this.setUID($bel.children());
640                                rootSelector = this.filterByNode($bel.children());
641                                $.log("New rootSelector: " + rootSelector);
642                                //replace transform with unique selector
643                                var nhtml2 = $bel.html();
644                                nhtml2 = this.unwrapAttrs(nhtml2);
645                                var obhtml = this.unwrapAttrs(bhtml);
646
647
648                                ob.transform[nhtml2] = bbcode;
649                                delete ob.transform[obhtml];
650
651                                bhtml = nhtml2;
652                                orightml = nhtml2;
653                            }
654
655                            //create root selector for isContain
656                            if (!ob.excmd) {
657                                if (!ob.rootSelector) {
658                                    ob.rootSelector = [];
659                                }
660                                ob.rootSelector.push(rootSelector);
661                            }
662
663                            //check for rules on this rootSeletor
664                            if (typeof(o.rules[rootSelector]) == "undefined") {
665                                o.rules[rootSelector] = [];
666                            }
667                            var crules = {};
668
669                            if (bhtml.match(/\{\S+?\}/)) {
670                                $bel.find('*').each($.proxy(function (idx, el) {
671                                    //check attributes
672
673                                    var attributes = this.getAttributeList(el);
674                                    $.each(attributes, $.proxy(function (i, item) {
675                                        var attr = $(el).attr(item);
676                                        if (item.substr(0, 1) == '_') {
677                                            item = item.substr(1);
678                                        }
679
680                                        var r = attr.match(/\{\S+?\}/g);
681                                        if (r) {
682                                            for (var a = 0; a < r.length; a++) {
683                                                var rname = r[a].substr(1, r[a].length - 2);
684                                                rname = rname.replace(this.getValidationRGX(rname), "");
685                                                var p = this.relFilterByNode(el, rootSelector);
686                                                var regRepl = (attr != r[a]) ? this.getRegexpReplace(attr, r[a]) : false;
687                                                crules[rname.toLowerCase()] = {sel: (p) ? $.trim(p) : false, attr: item, rgx: regRepl}
688                                            }
689                                        }
690                                    }, this));
691
692                                    //check for text
693                                    var sl = [];
694                                    if (!$(el).is("iframe")) {
695                                        $(el).contents().filter(function () {
696                                            return this.nodeType === 3
697                                        }).each($.proxy(function (i, rel) {
698                                            var txt = rel.textContent || rel.data;
699                                            if (typeof(txt) == "undefined") {
700                                                return true;
701                                            }
702                                            var r = txt.match(/\{\S+?\}/g);
703                                            if (r) {
704                                                for (var a = 0; a < r.length; a++) {
705                                                    var rname = r[a].substr(1, r[a].length - 2);
706                                                    rname = rname.replace(this.getValidationRGX(rname), "");
707                                                    var p = this.relFilterByNode(el, rootSelector);
708                                                    var regRepl = (txt != r[a]) ? this.getRegexpReplace(txt, r[a]) : false;
709                                                    var sel = (p) ? $.trim(p) : false;
710                                                    if ($.inArray(sel, sl) > -1 || $(rel).parent().contents().size() > 1) {
711                                                        //has dublicate and not one children, need wrap
712                                                        var nel = $("<span>").html("{" + rname + "}");
713                                                        this.setUID(nel, "wbb");
714                                                        var start = (txt.indexOf(rname) + rname.length) + 1;
715                                                        var after_txt = txt.substr(start, txt.length - start);
716                                                        //create wrap element
717                                                        rel.data = txt.substr(0, txt.indexOf(rname) - 1);
718                                                        $(rel).after(this.elFromString(after_txt, document)).after(nel);
719
720                                                        sel = ((sel) ? sel + " " : "") + this.filterByNode(nel);
721                                                        regRepl = false;
722                                                    }
723                                                    crules[rname.toLowerCase()] = {sel: sel, attr: false, rgx: regRepl};
724                                                    sl[sl.length] = sel;
725                                                }
726                                            }
727                                        }, this));
728                                    }
729                                    sl = null;
730
731
732                                }, this));
733
734                                var nbhtml = $bel.html();
735                                //UnWrap attributes 
736                                nbhtml = this.unwrapAttrs(nbhtml);
737                                if (orightml != nbhtml) {
738                                    //if we modify html, replace it
739                                    delete ob.transform[orightml];
740                                    ob.transform[nbhtml] = bbcode;
741                                    bhtml = nbhtml;
742                                }
743
744                            }
745                            o.rules[rootSelector].push([bbcode, crules]);
746
747                            //check for onlyClearText
748                            if (ob.onlyClearText === true) {
749                                if (!this.cleartext) {
750                                    this.cleartext = {};
751                                }
752                                this.cleartext[rootSelector] = btnlist[bidx];
753                            }
754
755                            //check for groupkey
756                            if (ob.groupkey) {
757                                if (!o.groups[ob.groupkey]) {
758                                    o.groups[ob.groupkey] = []
759                                }
760                                o.groups[ob.groupkey].push(rootSelector);
761                            }
762                        }
763                    }
764
765                    //sort rootSelector
766                    if (ob.rootSelector) {
767                        this.sortArray(ob.rootSelector, -1);
768                    }
769
770                    var htmll = $.map(ob.transform,function (bb, html) {
771                        return html
772                    }).sort(function (a, b) {
773                        return ((b[0] || "").length - (a[0] || "").length)
774                    });
775                    ob.bbcode = ob.transform[htmll[0]];
776                    ob.html = htmll[0];
777                }
778            }
779            this.options.btnlist = btnlist; //use for transforms, becouse select elements not present in buttons
780
781            //add custom rules, for table,tr,td and other
782            $.extend(o.rules, this.options.customRules);
783
784            //smile rules
785            o.srules = {};
786            if (this.options.smileList) {
787                $.each(o.smileList, $.proxy(function (i, sm) {
788                    var $sm = $(this.strf(sm.img, o));
789                    var f = this.filterByNode($sm);
790                    o.srules[f] = [sm.bbcode, sm.img];
791                }, this));
792            }
793
794            //sort transforms by bbcode length desc
795            for (var rootsel in o.rules) {
796                this.options.rules[rootsel].sort(function (a, b) {
797                    return (b[0].length - a[0].length)
798                });
799            }
800
801            //create rootsel list
802            this.rsellist = [];
803            for (var rootsel in this.options.rules) {
804                this.rsellist.push(rootsel);
805            }
806            this.sortArray(this.rsellist, -1);
807        },
808
809        //BUILD
810        build: function () {
811            $.log("Build editor");
812
813            //this.$editor = $('<div class="wysibb">');
814            this.$editor = $('<div>').addClass("wysibb");
815
816            if (this.isMobile) {
817                this.$editor.addClass("wysibb-mobile");
818            }
819
820            //set direction if defined
821            if (this.options.direction) {
822                this.$editor.css("direction", this.options.direction)
823            }
824
825            this.$editor.insertAfter(this.txtArea).append(this.txtArea);
826
827            this.startHeight = this.$txtArea.outerHeight();
828            this.$txtArea.addClass("wysibb-texarea");
829            this.buildToolbar();
830            //Build iframe if needed
831            this.$txtArea.wrap('<div class="wysibb-text">');
832
833            if (this.options.onlyBBmode === false) {
834                var height = this.options.minheight || this.$txtArea.outerHeight();
835                var maxheight = this.options.resize_maxheight;
836                var mheight = (this.options.autoresize === true) ? this.options.resize_maxheight : height;
837                this.$body = $(this.strf('<div class="wysibb-text-editor" style="max-height:{maxheight}px;min-height:{height}px"></div>', {maxheight: mheight, height: height})).insertAfter(this.$txtArea);
838                this.body = this.$body[0];
839                this.$txtArea.hide();
840
841                if (height > 32) {
842                    this.$toolbar.css("max-height", height);
843                }
844
845                $.log("WysiBB loaded");
846
847                this.$body.addClass("wysibb-body").addClass(this.options.bodyClass);
848
849                //set direction if defined
850                if (this.options.direction) {
851                    this.$body.css("direction", this.options.direction)
852                }
853
854
855                if ('contentEditable' in this.body) {
856                    this.body.contentEditable = true;
857                    try {
858                        //fix for mfirefox
859                        //document.execCommand('enableObjectResizing', false, 'false'); //disable image resizing
860                        document.execCommand('StyleWithCSS', false, false);
861                        //document.designMode = "on";
862                        this.$body.append("<span></span>");
863                    } catch (e) {
864                    }
865                } else {
866                    //use onlybbmode
867                    this.options.onlyBBmode = this.options.bbmode = true;
868                }
869
870                //check for exist content in textarea
871                if (this.txtArea.value.length > 0) {
872                    this.txtAreaInitContent();
873                }
874
875
876                //clear html on paste from external editors
877                this.$body.bind('keydown', $.proxy(function (e) {
878                    if ((e.which == 86 && (e.ctrlKey == true || e.metaKey == true)) || (e.which == 45 && (e.shiftKey == true || e.metaKey == true))) {
879                        if (!this.$pasteBlock) {
880                            this.saveRange();
881                            this.$pasteBlock = $(this.elFromString('<div style="opacity:0;" contenteditable="true">\uFEFF</div>'));
882
883                            this.$pasteBlock.appendTo(this.body);
884                            //if (!$.support.search?type=2) {this.$pasteBlock.focus();} //IE 7,8 FIX
885                            setTimeout($.proxy(function () {
886                                    this.clearPaste(this.$pasteBlock);
887                                    var rdata = '<span>' + this.$pasteBlock.html() + '</span>';
888                                    this.$body.attr("contentEditable", "true");
889                                    this.$pasteBlock.blur().remove();
890                                    this.body.focus();
891
892                                    if (this.cleartext) {
893                                        $.log("Check if paste to clearText Block");
894                                        if (this.isInClearTextBlock()) {
895                                            rdata = this.toBB(rdata).replace(/\n/g, "<br/>").replace(/\s{3}/g, '<span class="wbbtab"></span>');
896                                        }
897                                    }
898                                    rdata = rdata.replace(/\t/g, '<span class="wbbtab"></span>');
899                                    this.selectRange(this.lastRange);
900                                    this.insertAtCursor(rdata, false);
901                                    this.lastRange = false;
902                                    this.$pasteBlock = false;
903                                }
904                                , this), 1);
905                            this.selectNode(this.$pasteBlock[0]);
906                        }
907                        return true;
908                    }
909                }, this));
910
911                //insert BR on press enter
912                this.$body.bind('keydown', $.proxy(function (e) {
913                    if (e.which == 13) {
914                        var isLi = this.isContain(this.getSelectNode(), 'li');
915                        if (!isLi) {
916                            if (e.preventDefault) {
917                                e.preventDefault();
918                            }
919                            this.checkForLastBR(this.getSelectNode());
920                            this.insertAtCursor('<br/>', false);
921                        }
922                    }
923                }, this));
924
925                //tabInsert
926                if (this.options.tabInsert === true) {
927                    this.$body.bind('keydown', $.proxy(this.pressTab, this));
928                }
929
930                //add event listeners
931                this.$body.bind('mouseup keyup', $.proxy(this.updateUI, this));
932                this.$body.bind('mousedown', $.proxy(function (e) {
933                    this.clearLastRange();
934                    this.checkForLastBR(e.target)
935                }, this));
936
937                //trace Textarea
938                if (this.options.traceTextarea === true) {
939                    $(document).bind("mousedown", $.proxy(this.traceTextareaEvent, this));
940                    this.$txtArea.val("");
941                }
942
943                //attach hotkeys
944                if (this.options.hotkeys === true) {
945                    this.$body.bind('keydown', $.proxy(this.presskey, this));
946                }
947
948                //smileConversion
949                if (this.options.smileConversion === true) {
950                    this.$body.bind('keyup', $.proxy(this.smileConversion, this));
951                }
952
953                this.inited = true;
954
955                //create resize lines
956                if (this.options.autoresize === true) {
957                    this.$bresize = $(this.elFromString('<div class="bottom-resize-line"></div>')).appendTo(this.$editor)
958                        .wdrag({
959                            scope: this,
960                            axisY: true,
961                            height: height
962                        });
963                }
964
965                this.imgListeners();
966            }
967
968
969            //this.$editor.append('<span class="powered">Powered by <a href="http://www.wysibb.com" target="_blank">WysiBB<a/></span>');
970
971            //add event listeners to textarea 
972            this.$txtArea.bind('mouseup keyup', $.proxy(function () {
973                clearTimeout(this.uitimer);
974                this.uitimer = setTimeout($.proxy(this.updateUI, this), 100);
975            }, this));
976
977            //attach hotkeys
978            if (this.options.hotkeys === true) {
979                $(document).bind('keydown', $.proxy(this.presskey, this));
980            }
981        },
982        buildToolbar: function () {
983            if (this.options.toolbar === false) {
984                return false;
985            }
986
987            //this.$toolbar = $('<div class="wysibb-toolbar">').prependTo(this.$editor);
988            this.$toolbar = $('<div>').addClass("wysibb-toolbar").prependTo(this.$editor);
989
990            var $btnContainer;
991            $.each(this.options.buttons, $.proxy(function (i, bn) {
992                var opt = this.options.allButtons[bn];
993                if (i == 0 || bn == "|" || bn == "-") {
994                    if (bn == "-") {
995                        this.$toolbar.append("<div>");
996                    }
997                    $btnContainer = $('<div class="wysibb-toolbar-container">').appendTo(this.$toolbar);
998                }
999                if (opt) {
1000                    if (opt.type == "colorpicker") {
1001                        this.buildColorpicker($btnContainer, bn, opt);
1002                    } else if (opt.type == "table") {
1003                        this.buildTablepicker($btnContainer, bn, opt);
1004                    } else if (opt.type == "select") {
1005                        this.buildSelect($btnContainer, bn, opt);
1006                    } else if (opt.type == "smilebox") {
1007                        this.buildSmilebox($btnContainer, bn, opt);
1008                    } else {
1009                        this.buildButton($btnContainer, bn, opt);
1010                    }
1011                }
1012            }, this));
1013
1014            //fix for hide tooltip on quick mouse over
1015            this.$toolbar.find(".btn-tooltip").hover(function () {
1016                $(this).parent().css("overflow", "hidden")
1017            }, function () {
1018                $(this).parent().css("overflow", "visible")
1019            });
1020
1021            //build bbcode switch button
1022            //var $bbsw = $('<div class="wysibb-toolbar-container modeSwitch"><div class="wysibb-toolbar-btn" unselectable="on"><span class="btn-inner ve-tlb-bbcode" unselectable="on"></span></div></div>').appendTo(this.$toolbar);
1023            var $bbsw = $(document.createElement('div')).addClass("wysibb-toolbar-container modeSwitch").html('<div class="wysibb-toolbar-btn mswitch" unselectable="on"><span class="btn-inner modesw" unselectable="on">[bbcode]</span></div>').appendTo(this.$toolbar);
1024            if (this.options.bbmode == true) {
1025                $bbsw.children(".wysibb-toolbar-btn").addClass("on");
1026            }
1027            if (this.options.onlyBBmode === false) {
1028                $bbsw.children(".wysibb-toolbar-btn").click($.proxy(function (e) {
1029                    $(e.currentTarget).toggleClass("on");
1030                    this.modeSwitch();
1031                }, this));
1032            }
1033        },
1034        buildButton: function (container, bn, opt) {
1035            if (typeof(container) != "object") {
1036                container = this.$toolbar;
1037            }
1038            var btnHTML = (opt.buttonHTML) ? $(this.strf(opt.buttonHTML, this.options)).addClass("btn-inner") : this.strf('<span class="btn-inner btn-text">{text}</span>', {text: opt.buttonText.replace(/</g, "&lt;")});
1039            var hotkey = (this.options.hotkeys === true && this.options.showHotkeys === true && opt.hotkey) ? (' <span class="tthotkey">[' + opt.hotkey + ']</span>') : "";
1040            var $btn = $('<div class="wysibb-toolbar-btn wbb-' + bn + '">').appendTo(container).append(btnHTML).append(this.strf('<span class="btn-tooltip">{title}<ins/>{hotkey}</span>', {title: opt.title, hotkey: hotkey}));
1041
1042            //attach events
1043            this.controllers.push($btn);
1044            $btn.bind('queryState', $.proxy(function (e) {
1045                (this.queryState(bn)) ? $(e.currentTarget).addClass("on") : $(e.currentTarget).removeClass("on");
1046            }, this));
1047            $btn.mousedown($.proxy(function (e) {
1048                e.preventDefault();
1049                this.execCommand(bn, opt.exvalue || false);
1050                $(e.currentTarget).trigger('queryState');
1051            }, this));
1052        },
1053        buildColorpicker: function (container, bn, opt) {
1054            var $btn = $('<div class="wysibb-toolbar-btn wbb-dropdown wbb-cp">').appendTo(container).append('<div class="ve-tlb-colorpick"><span class="fonticon">\uE010</span><span class="cp-line"></span></div><ins class="fonticon ar">\uE011</ins>').append(this.strf('<span class="btn-tooltip">{title}<ins/></span>', {title: opt.title}));
1055            var $cpline = $btn.find(".cp-line");
1056
1057            var $dropblock = $('<div class="wbb-list">').appendTo($btn);
1058            $dropblock.append('<div class="nc">' + CURLANG.auto + '</div>');
1059            var colorlist = (opt.colors) ? opt.colors.split(",") : [];
1060            for (var j = 0; j < colorlist.length; j++) {
1061                colorlist[j] = $.trim(colorlist[j]);
1062                if (colorlist[j] == "-") {
1063                    //insert padding
1064                    $dropblock.append('<span class="pl"></span>');
1065                } else {
1066                    $dropblock.append(this.strf('<div class="sc" style="background:{color}" title="{color}"></div>', {color: colorlist[j]}));
1067                }
1068            }
1069            var basecolor = $(document.body).css("color");
1070            //attach events
1071            this.controllers.push($btn);
1072            $btn.bind('queryState', $.proxy(function (e) {
1073                //queryState
1074                $cpline.css("background-color", basecolor);
1075                var r = this.queryState(bn, true);
1076                if (r) {
1077                    $cpline.css("background-color", (this.options.bbmode) ? r.color : r);
1078                    $btn.find(".ve-tlb-colorpick span.fonticon").css("color", (this.options.bbmode) ? r.color : r);
1079                }
1080            }, this));
1081            $btn.mousedown($.proxy(function (e) {
1082                e.preventDefault();
1083                this.dropdownclick(".wbb-cp", ".wbb-list", e);
1084            }, this));
1085            $btn.find(".sc").mousedown($.proxy(function (e) {
1086                e.preventDefault();
1087                this.selectLastRange();
1088                var c = $(e.currentTarget).attr("title");
1089                this.execCommand(bn, c);
1090                $btn.trigger('queryState');
1091            }, this));
1092            $btn.find(".nc").mousedown($.proxy(function (e) {
1093                e.preventDefault();
1094                this.selectLastRange();
1095                this.execCommand(bn, basecolor);
1096                $btn.trigger('queryState');
1097            }, this));
1098            $btn.mousedown(function (e) {
1099                if (e.preventDefault) e.preventDefault();
1100            });
1101        },
1102        buildTablepicker: function (container, bn, opt) {
1103            var $btn = $('<div class="wysibb-toolbar-btn wbb-dropdown wbb-tbl">').appendTo(container).append('<span class="btn-inner fonticon ve-tlb-table1">\uE00e</span><ins class="fonticon ar">\uE011</ins>').append(this.strf('<span class="btn-tooltip">{title}<ins/></span>', {title: opt.title}));
1104
1105            var $listblock = $('<div class="wbb-list">').appendTo($btn);
1106            var $dropblock = $('<div>').css({"position": "relative", "box-sizing": "border-box"}).appendTo($listblock);
1107            var rows = opt.rows || 10;
1108            var cols = opt.cols || 10;
1109            var allcount = rows * cols;
1110            $dropblock.css("height", (rows * opt.cellwidth + 2) + "px");
1111            for (var j = 1; j <= cols; j++) {
1112                for (var h = 1; h <= rows; h++) {
1113                    //var html = this.strf('<div class="tbl-sel" style="width:{width}px;height:{height}px;z-index:{zindex}" title="{row},{col}"></div>',{width: (j*opt.cellwidth),height: (h*opt.cellwidth),zindex: --allcount,row:h,col:j});
1114                    var html = '<div class="tbl-sel" style="width:' + (j * 100 / cols
1114) + '%;height:' + (h * 100 / rows) + '%;z-index:' + (--allcount) + '" title="' + h + ',' + j + '"></div>';
1115                    $dropblock.append(html);
1116                }
1117            }
1118            //this.debug("Attach event on: tbl-sel");
1119            $btn.find(".tbl-sel").mousedown($.proxy(function (e) {
1120                e.preventDefault();
1121                var t = $(e.currentTarget).attr("title");
1122                var rc = t.split(",");
1123                var code = (this.options.bbmode) ? '[table]' : '<table class="wbb-table" cellspacing="5" cellpadding="0">';
1124                for (var i = 1; i <= rc[0]; i++) {
1125                    code += (this.options.bbmode) ? ' [tr]\n' : '<tr>';
1126                    for (var j = 1; j <= rc[1]; j++) {
1127                        code += (this.options.bbmode) ? '  [td][/td]\n' : '<td>\uFEFF</td>';
1128                    }
1129                    code += (this.options.bbmode) ? '[/tr]\n' : '</tr>';
1130                }
1131                code += (this.options.bbmode) ? '[/table]' : '</table>';
1132                this.insertAtCursor(code);
1133            }, this));
1134            //this.debug("END Attach event on: tbl-sel");
1135            $btn.mousedown($.proxy(function (e) {
1136                e.preventDefault();
1137                this.dropdownclick(".wbb-tbl", ".wbb-list", e);
1138            }, this));
1139
1140        },
1141        buildSelect: function (container, bn, opt) {
1142            var $btn = $('<div class="wysibb-toolbar-btn wbb-select wbb-' + bn + '">').appendTo(container).append(this.strf('<span class="val">{title}</span><ins class="fonticon sar">\uE012</ins>', opt)).append(this.strf('<span class="btn-tooltip">{title}<ins/></span>', {title: opt.title}));
1143            var $sblock = $('<div class="wbb-list">').appendTo($btn);
1144            var $sval = $btn.find("span.val");
1145
1146            var olist = ($.isArray(opt.options)) ? opt.options : opt.options.split(",");
1147            var $selectbox = (this.isMobile) ? $("<select>").addClass("wbb-selectbox") : "";
1148            for (var i = 0; i < olist.length; i++) {
1149                var oname = olist[i];
1150                if (typeof(oname) == "string") {
1151                    var option = this.options.allButtons[oname];
1152                    if (option) {
1153                        //$.log("create: "+oname); 
1154                        if (option.html) {
1155                            $('<span>').addClass("option").attr("oid", oname).attr("cmdvalue", option.exvalue).appendTo($sblock).append(this.strf(option.html, {seltext: option.title}));
1156                        } else {
1157                            $sblock.append(this.strf('<span class="option" oid="' + oname + '" cmdvalue="' + option.exvalue + '">{title}</span>', option));
1158                        }
1159
1160                        //SelectBox for mobile devices
1161                        if (this.isMobile) {
1162                            $selectbox.append($('<option>').attr("oid", oname).attr("cmdvalue", option.exvalue).append(option.title));
1163                        }
1164                    }
1165                } else {
1166                    //build option list from array
1167                    var params = {
1168                        seltext: oname.title
1169                    };
1170                    params[opt.valueBBname] = oname.exvalue;
1171                    $('<span>').addClass("option").attr("oid", bn).attr("cmdvalue", oname.exvalue).appendTo($sblock).append(this.strf(opt.html, params));
1172
1173                    if (this.isMobile) {
1174                        $selectbox.append($('<option>').attr("oid", bn).attr("cmdvalue", oname.exvalue).append(oname.exvalue))
1175                    }
1176                }
1177            }
1178            //$sblock.append($selectbox);
1179            if (this.isMobile) {
1180                $selectbox.appendTo(container);
1181                this.controllers.push($selectbox);
1182
1183                $selectbox.bind('queryState', $.proxy(function (e) {
1184                    //queryState
1185                    $selectbox.find("option").each($.proxy(function (i, el) {
1186                        var $el = $(el);
1187                        var r = this.queryState($el.attr("oid"), true);
1188                        var cmdvalue = $el.attr("cmdvalue");
1189                        if ((cmdvalue && r == $el.attr("cmdvalue")) || (!cmdvalue && r)) {
1190                            $el.prop("selected", true);
1191                            return false;
1192                        }
1193                    }, this));
1194                }, this));
1195
1196                $selectbox.change($.proxy(function (e) {
1197                    e.preventDefault();
1198                    var $o = $(e.currentTarget).find(":selected");
1199                    var oid = $o.attr("oid");
1200                    var cmdvalue = $o.attr("cmdvalue");
1201                    var opt = this.options.allButtons[oid];
1202                    this.execCommand(oid, opt.exvalue || cmdvalue || false);
1203                    $(e.currentTarget).trigger('queryState');
1204                }, this));
1205
1206            }
1207            this.controllers.push($btn);
1208            $btn.bind('queryState', $.proxy(function (e) {
1209                //queryState
1210                $sval.text(opt.title);
1211                $btn.find(".option.selected").removeClass("selected");
1212                $btn.find(".option").each($.proxy(function (i, el) {
1213                    var $el = $(el);
1214                    var r = this.queryState($el.attr("oid"), true);
1215                    var cmdvalue = $el.attr("cmdvalue");
1216                    if ((cmdvalue && r == $el.attr("cmdvalue")) || (!cmdvalue && r)) {
1217                        $sval.text($el.text());
1218                        $el.addClass("selected");
1219                        return false;
1220                    }
1221                }, this));
1222            }, this));
1223            $btn.mousedown($.proxy(function (e) {
1224                e.preventDefault();
1225                this.dropdownclick(".wbb-select", ".wbb-list", e);
1226            }, this));
1227            $btn.find(".option").mousedown($.proxy(function (e) {
1228                e.preventDefault();
1229                var oid = $(e.currentTarget).attr("oid");
1230                var cmdvalue = $(e.currentTarget).attr("cmdvalue");
1231                var opt = this.options.allButtons[oid];
1232                this.execCommand(oid, opt.exvalue || cmdvalue || false);
1233                $(e.currentTarget).trigger('queryState');
1234            }, this));
1235        },
1236        buildSmilebox: function (container, bn, opt) {
1237            if (this.options.smileList && this.options.smileList.length > 0) {
1238                var $btnHTML = $(this.strf(opt.buttonHTML, opt)).addClass("btn-inner");
1239                var $btn = $('<div class="wysibb-toolbar-btn wbb-smilebox wbb-' + bn + '">').appendTo(container).append($btnHTML).append(this.strf('<span class="btn-tooltip">{title}<ins/></span>', {title: opt.title}));
1240                var $sblock = $('<div class="wbb-list">').appendTo($btn);
1241                if ($.isArray(this.options.smileList)) {
1242                    $.each(this.options.smileList, $.proxy(function (i, sm) {
1243                        $('<span>').addClass("smile").appendTo($sblock).append($(this.strf(sm.img, this.options)).attr("title", sm.title));
1244                    }, this));
1245                }
1246                $btn.mousedown($.proxy(function (e) {
1247                    e.preventDefault();
1248                    this.dropdownclick(".wbb-smilebox", ".wbb-list", e);
1249                }, this));
1250                $btn.find('.smile').mousedown($.proxy(function (e) {
1251                    e.preventDefault();
1252                    //this.selectLastRange();
1253                    this.insertAtCursor((this.options.bbmode) ? this.toBB($(e.currentTarget).html()) : $($(e.currentTarget).html()));
1254                }, this))
1255            }
1256        },
1257        updateUI: function (e) {
1258            if (!e || ((e.which >= 8 && e.which <= 46) || e.which > 90 || e.type == "mouseup")) {
1259                $.each(this.controllers, $.proxy(function (i, $btn) {
1260                    $btn.trigger('queryState');
1261                }, this));
1262            }
1263
1264            //check for onlyClearText
1265            this.disNonActiveButtons();
1266
1267        },
1268        initModal: function () {
1269            this.$modal = $("#wbbmodal");
1270            if (this.$modal.size() == 0) {
1271                $.log("Init modal");
1272                this.$modal = $('<div>').attr("id", "wbbmodal").prependTo(document.body)
1273                    .html('<div class="wbbm"><div class="wbbm-title"><span class="wbbm-title-text"></span><span class="wbbclose" title="' + CURLANG.close + '">?</span></div><div class="wbbm-content"></div><div class="wbbm-bottom"><button id="wbbm-submit" class="wbb-button">' + CURLANG.save + '</button><button id="wbbm-cancel" class="wbb-cancel-button">' + CURLANG.cancel + '</button><button id="wbbm-remove" class="wbb-remove-button">' + CURLANG.remove + '</button></div></div>').hide();
1274
1275                this.$modal.find('#wbbm-cancel,.wbbclose').click($.proxy(this.closeModal, this));
1276                this.$modal.bind('click', $.proxy(function (e) {
1277                    if ($(e.target).parents(".wbbm").size() == 0) {
1278                        this.closeModal();
1279                    }
1280                }, this));
1281
1282                $(document).bind("keydown", $.proxy(this.escModal, this)); //ESC key close modal
1283            }
1284        },
1285        initHotkeys: function () {
1286            $.log("initHotkeys");
1287            this.hotkeys = [];
1288            var klist = "0123456789       abcdefghijklmnopqrstuvwxyz";
1289            $.each(this.options.allButtons, $.proxy(function (cmd, opt) {
1290                if (opt.hotkey) {
1291                    var keys = opt.hotkey.split("+");
1292                    if (keys && keys.length >= 2) {
1293                        var metasum = 0;
1294                        var key = keys.pop();
1295                        $.each(keys, function (i, k) {
1296                            switch ($.trim(k.toLowerCase())) {
1297                                case "ctrl":
1298                                {
1299                                    metasum += 1;
1300                                    break;
1301                                }
1302                                case "shift":
1303                                {
1304                                    metasum += 4;
1305                                    break;
1306                                }
1307                                case "alt":
1308                                {
1309                                    metasum += 7;
1310                                    break;
1311                                }
1312                            }
1313                        });
1314                        //$.log("metasum: "+metasum+" key: "+key+" code: "+(klist.
1314indexOf(key)+48));
1315                        if (metasum > 0) {
1316                            if (!this.hotkeys["m" + metasum]) {
1317                                this.hotkeys["m" + metasum] = [];
1318                            }
1319                            this.hotkeys["m" + metasum]["k" + (klist.indexOf(key) + 48)] = cmd;
1320                        }
1321                    }
1322                }
1323            }, this))
1324        },
1325        presskey: function (e) {
1326            if (e.ctrlKey == true || e.shiftKey == true || e.altKey == true) {
1327                var metasum = ((e.ctrlKey == true) ? 1 : 0) + ((e.shiftKey == true) ? 4 : 0) + ((e.altKey == true) ? 7 : 0);
1328                if (this.hotkeys["m" + metasum] && this.hotkeys["m" + metasum]["k" + e.which]) {
1329                    this.execCommand(this.hotkeys["m" + metasum]["k" + e.which], false);
1330                    e.preventDefault();
1331                    return false;
1332                }
1333            }
1334        },
1335
1336        //COgdfMMAND FUNCTIONS
1337        execCommand: function (command, value) {
1338            $.log("execCommand: " + command);
1339            var opt = this.options.allButtons[command];
1340            if (opt.en !== true) {
1341                return false;
1342            }
1343            var queryState = this.queryState(command, value);
1344
1345            //check for onlyClearText
1346            var skipcmd = this.isInClearTextBlock();
1347            if (skipcmd && skipcmd != command) {
1348                return;
1349            }
1350
1351
1352            if (opt.excmd) {
1353                //use NativeCommand
1354                if (this.options.bbmode) {
1355                    $.log("Native command in bbmode: " + command);
1356                    if (queryState && opt.subInsert != true) {
1357                        //remove bbcode
1358                        this.wbbRemoveCallback(command, value);
1359                    } else {
1360                        //insert bbcode
1361                        var v = {};
1362                        if (opt.valueBBname && value) {
1363                            v[opt.valueBBname] = value;
1364                        }
1365                        this.insertAtCursor(this.getBBCodeByCommand(command, v));
1366                    }
1367                } else {
1368                    this.execNativeCommand(opt.excmd, value || false);
1369                }
1370            } else if (!opt.cmd) {
1371                //wbbCommand
1372                //this.wbbExecCommand(command,value,queryState,$.proxy(this.wbbInsertCallback,this),$.proxy(this.wbbRemoveCallback,this));
1373                this.wbbExecCommand.call(this, command, value, queryState);
1374            } else {
1375                //user custom command
1376                opt.cmd.call(this, command, value, queryState);
1377            }
1378            this.updateUI();
1379        },
1380        queryState: function (command, withvalue) {
1381            var opt = this.options.allButtons[command];
1382            if (opt.en !== true) {
1383                return false;
1384            }
1385            //if (opt.subInsert===true && opt.type!="colorpicker") {return false;}
1386            if (this.options.bbmode) {
1387                //bbmode
1388                if (opt.bbSelector) {
1389                    for (var i = 0; i < opt.bbSelector.length; i++) {
1390                        var b = this.isBBContain(opt.bbSelector[i]);
1391                        if (b) {
1392                            return this.getParams(b, opt.bbSelector[i], b[1]);
1393                        }
1394                    }
1395                }
1396                return false;
1397            } else {
1398                if (opt.excmd) {
1399                    //native command
1400                    if (withvalue) {
1401                        try {
1402                            //Firefox fix
1403                            var v = (document.queryCommandValue(opt.excmd) + "").replace(/\'/g, "");
1404                            if (opt.excmd == "foreColor") {
1405                                v = this.rgbToHex(v);
1406                            }
1407                            //return (v==value);
1408                            return v;
1409                        } catch (e) {
1410                            return false;
1411                        }
1412                    } else {
1413                        try { //Firefox fix, exception while get queryState for UnorderedList
1414                            if ((opt.excmd == "bold" || opt.excmd == "italic" || opt.excmd == "underline" || opt.excmd == "strikeThrough") && $(this.getSelectNode()).is("img")) { //Fix, when img selected
1415                                return false;
1416                            } else if (opt.excmd == "underline" && $(this.getSelectNode()).closest("a").size() > 0) { //fix, when link select
1417                                return false;
1418                            }
1419                            return document.queryCommandState(opt.excmd);
1420                        } catch (e) {
1421                            return false;
1422                        }
1423                    }
1424                } else {
1425                    //custom command
1426                    if ($.isArray(opt.rootSelector)) {
1427                        for (var i = 0; i < opt.rootSelector.length; i++) {
1428                            var n = this.isContain(this.getSelectNode(), opt.rootSelector[i]);
1429                            if (n) {
1430                                return this.getParams(n, opt.rootSelector[i]);
1431                            }
1432                        }
1433                    }
1434                    return false;
1435                }
1436            }
1437        },
1438        wbbExecCommand: function (command, value, queryState) { //default command for custom bbcodes
1439            $.log("wbbExecCommand");
1440            var opt = this.options.allButtons[command];
1441            if (opt) {
1442                if (opt.modal) {
1443                    if ($.isFunction(opt.modal)) {
1444                        //custom modal function
1445                        //opt.modal(command,opt.modal,queryState,new clbk(this));
1446                        opt.modal.call(this, command, opt.modal, queryState);
1447                    } else {
1448                        this.showModal.call(this, command, opt.modal, queryState);
1449                    }
1450                } else {
1451                    if (queryState && opt.subInsert != true) {
1452                        //remove formatting
1453                        //removeCallback(command,value);
1454                        this.wbbRemoveCallback(command);
1455                    } else {
1456                        //insert format
1457                        if (opt.groupkey) {
1458                            var groupsel = this.options.groups[opt.groupkey];
1459                            if (groupsel) {
1460                                var snode = this.getSelectNode();
1461                                $.each(groupsel, $.proxy(function (i, sel) {
1462                                    var is = this.isContain(snode, sel);
1463                                    if (is) {
1464                                        var $sp = $('<span>').html(is.innerHTML);
1465                                        var id = this.setUID($sp);
1466                                        $(is).replaceWith($sp);
1467                                        this.selectNode(this.$editor.find("#" + id)[0]);
1468                                        return false;
1469                                    }
1470                                }, this));
1471                            }
1472                        }
1473                        this.wbbInsertCallback(command, value)
1474                    }
1475                }
1476            }
1477        },
1478        wbbInsertCallback: function (command, paramobj) {
1479            if (typeof(paramobj) != "object") {
1480                paramobj = {}
1481            }
1482            $.log("wbbInsertCallback: " + command);
1483            var data = this.getCodeByCommand(command, paramobj);
1484            this.insertAtCursor(data);
1485
1486            if (this.seltextID && data.indexOf(this.seltextID) != -1) {
1487                var snode = this.$body.find("#" + this.seltextID)[0];
1488                this.selectNode(snode);
1489                $(snode).removeAttr("id");
1490                this.seltextID = false;
1491            }
1492        },
1493        wbbRemoveCallback: function (command, clear) {
1494            $.log("wbbRemoveCallback: " + command);
1495            var opt = this.options.allButtons[command];
1496            if (this.options.bbmode) {
1497                //bbmode
1498                //REMOVE BBCODE
1499                var pos = this.getCursorPosBB();
1500                var stextnum = 0;
1501                $.each(opt.bbSelector, $.proxy(function (i, bbcode) {
1502                    var stext = bbcode.match(/\{[\s\S]+?\}/g);
1503                    $.each(stext, function (n, s) {
1504                        if (s.toLowerCase() == "{seltext}") {
1505                            stextnum = n;
1506                            return false
1507                        }
1508                    });
1509                    var a = this.isBBContain(bbcode);
1510                    if (a) {
1511                        this.txtArea.value = this.txtArea.value.substr(0, a[1]) + this.txtArea.value.substr(a[1], this.txtArea.value.length - a[1]).replace(a[0][0], (clear === true) ? '' : a[0][stextnum + 1]);
1512                        this.setCursorPosBB(a[1]);
1513                        return false;
1514                    }
1515                }, this));
1516            } else {
1517                var node = this.getSelectNode();
1518                $.each(opt.rootSelector, $.proxy(function (i, s) {
1519                    //$.log("RS: "+s);
1520                    var root = this.isContain(node, s);
1521                    if (!root) {
1522                        return true;
1523                    }
1524                    var $root = $(root);
1525                    var cs = this.options.rules[s][0][1];
1526                    if ($root.is("span[wbb]") || !$root.is("span,font")) { //remove only blocks
1527                        if (clear === true || (!cs || !cs["seltext"])) {
1528                            this.setCursorByEl($root);
1529                            $root.remove();
1530                        } else {
1531                            if (cs && cs["seltext"] && cs["seltext"]["sel"]) {
1532                                var htmldata = $root.find(cs["seltext"]["sel"]).html();
1533                                if (opt.onlyClearText === true) {
1534                                    htmldata = this.getHTML(htmldata, true, true);
1535                                    htmldata = htmldata.replace(/\&#123;/g, "{").replace(/\&#125;/g, "}");
1536                                }
1537                                $root.replaceWith(htmldata);
1538                            } else {
1539                                var htmldata = $root.html();
1540                                if (opt.onlyClearText === true) {
1541                                    htmldata = this.getHTML(htmldata, true);
1542                                    htmldata = htmldata.replace(/\&lt;/g, "<").replace(/\&gt;/g, ">").replace(/\&#123;/g, "{").replace(/\&#125;/g, "}");
1543                                }
1544                                $root.replaceWith(htmldata);
1545                            }
1546                        }
1547                        return false;
1548                    } else {
1549                        //span,font - extract select content from this span,font
1550                        var rng = this.getRange();
1551                        var shtml = this.getSelectText();
1552                        var rnode = this.getSelectNode();
1553                        if (shtml == "") {
1554                            shtml = "\uFEFF";
1555                        } else {
1556                            shtml = this.clearFromSubInsert(shtml, command);
1557                        }
1558                        var ins = this.elFromString(shtml);
1559
1560                        var before_rng = (window.getSelection) ? rng.cloneRange() : this.body.createTextRange();
1561                        var after_rng = (window.getSelection) ? rng.cloneRange() : this.body.createTextRange();
1562
1563                        if (window.getSelection) {
1564                            this.insertAtCursor('<span id="wbbdivide"></span>');
1565                            var div = $root.find('span#wbbdivide').get(0);
1566                            before_rng.setStart(root.firstChild, 0);
1567                            before_rng.setEndBefore(div);
1568                            after_rng.setStartAfter(div);
1569                            after_rng.setEndAfter(root.lastChild);
1570                        } else {
1571                            before_rng.moveToElementText(root);
1572                            after_rng.moveToElementText(root);
1573                            before_rng.setEndPoint('EndToStart', rng);
1574                            after_rng.setEndPoint('StartToEnd', rng);
1575                        }
1576                        var bf = this.getSelectText(false, before_rng);
1577                        var af = this.getSelectText(false, after_rng);
1578                        if (af != "") {
1579                            var $af = $root.clone().html(af);
1580                            $root.after($af);
1581                        }
1582                        if (clear !== true) $root.after(ins); //insert select html
1583                        if (window.getSelection) {
1584                            $root.html(bf);
1585                            if (clear !== true) this.selectNode(ins);
1586                        } else {
1587                            $root.replaceWith(bf);
1588                        }
1589                        return false;
1590                    }
1591                }, this));
1592            }
1593        },
1594        execNativeCommand: function (cmd, param) {
1595            //$.log("execNativeCommand: '"+cmd+"' : "+param); 
1596            this.body.focus(); //set focus to frame body
1597            if (cmd == "insertHTML" && !window.getSelection) { //IE does't support insertHTML
1598                var r = (this.lastRange) ? this.lastRange : document.selection.createRange(); //IE 7,8 range lost fix
1599                r.pasteHTML(param);
1600                var txt = $('<div>').html(param).text(); //for ie selection inside block
1601                var brsp = txt.indexOf("\uFEFF");
1602                if (brsp > -1) {
1603                    r.moveStart('character', (-1) * (txt.length - brsp));
1604                    r.select();
1605                }
1606                this.lastRange = false;
1607            } else if (cmd == "insertHTML") { //fix webkit bug with insertHTML
1608                var sel = this.getSelection();
1609                var e = this.elFromString(param);
1610                var rng = (this.lastRange) ? this.lastRange : this.getRange();
1611                rng.deleteContents();
1612                rng.insertNode(e);
1613                rng.collapse(false);
1614                sel.removeAllRanges();
1615                sel.addRange(rng);
1616            } else {
1617                if (typeof param == "undefined") {
1618                    param = false;
1619                }
1620                if (this.lastRange) {
1621                    $.log("Last range select");
1622                    this.selectLastRange()
1623                }
1624                document.execCommand(cmd, false, param);
1625            }
1626
1627        },
1628        getCodeByCommand: function (command, paramobj) {
1629            return (this.options.bbmode) ? this.getBBCodeByCommand(command, paramobj) : this.getHTMLByCommand(command, paramobj);
1630        },
1631        getBBCodeByCommand: function (command, params) {
1632            if (!this.options.allButtons[command]) {
1633                return "";
1634            }
1635            if (typeof(params) == "undefined") {
1636                params = {};
1637            }
1638            params = this.keysToLower(params);
1639            if (!params["seltext"]) {
1640                //get selected text
1641                params["seltext"] = this.getSelectText(true);
1642            }
1643
1644            var bbcode = this.options.allButtons[command].bbcode;
1645            //bbcode = this.strf(bbcode,params);
1646            bbcode = bbcode.replace(/\{(.*?)(\[.*?\])*\}/g, function (str, p, vrgx) {
1647                if (vrgx) {
1648                    var vrgxp;
1649                    if (vrgx) {
1650                        vrgxp = new RegExp(vrgx + "+", "i");
1651                    }
1652                    if (typeof(params[p.toLowerCase()]) != "undefined" && params[p.toLowerCase()].toString().match(vrgxp) === null) {
1653                        //not valid value
1654                        return "";
1655                    }
1656                }
1657                return (typeof(params[p.toLowerCase()]) == "undefined") ? "" : params[p.toLowerCase()];
1658            });
1659
1660            //insert first with max params
1661            var rbbcode = null, maxpcount = 0;
1662            if (this.options.allButtons[command].transform) {
1663                var tr = [];
1664                $.each(this.options.allButtons[command].transform, function (html, bb) {
1665                    tr.push(bb);
1666                });
1667                tr = this.sortArray(tr, -1);
1668                $.each(tr, function (i, v) {
1669                    var valid = true, pcount = 0, pname = {};
1670                    v = v.replace(/\{(.*?)(\[.*?\])*\}/g, function (str, p, vrgx) {
1671                        var vrgxp;
1672                        p = p.toLowerCase();
1673                        if (vrgx) {
1674                            vrgxp = new RegExp(vrgx + "+", "i");
1675                        }
1676                        if (typeof(params[p.toLowerCase()]) == "undefined" || (vrgx && params[p.toLowerCase()].toString().match(vrgxp) === null)) {
1677                            valid = false;
1678                        }
1679                        if (typeof(params[p]) != "undefined" && !pname[p]) {
1680                            pname[p] = 1;
1681                            pcount++;
1682                        }
1683                        return (typeof(params[p.toLowerCase()]) == "undefined") ? "" : params[p.toLowerCase()];
1684                    });
1685                    if (valid && (pcount > maxpcount)) {
1686                        rbbcode = v;
1687                        maxpcount = pcount;
1688                    }
1689                });
1690            }
1691            return rbbcode || bbcode;
1692        },
1693        getHTMLByCommand: function (command, params) {
1694            if (!this.options.allButtons[command]) {
1695                return "";
1696            }
1697            params = this.keysToLower(params);
1698            if (typeof(params) == "undefined") {
1699                params = {};
1700            }
1701            if (!params["seltext"]) {
1702                //get selected text
1703                params["seltext"] = this.getSelectText(false);
1704                //$.log("seltext: '"+params["seltext"]+"'");
1705                if (params["seltext"] == "") {
1706                    params["seltext"] = "\uFEFF";
1707                }
1708                else {
1709                    //clear selection from current command tags
1710                    params["seltext"] = this.clearFromSubInsert(params["seltext"], command);
1711
1712                    //toBB if params onlyClearText=true
1713                    if (this.options.allButtons[command].onlyClearText === true) {
1714                        params["seltext"] = this.toBB(params["seltext"]).replace(/\</g, "&lt;").replace(/\n/g, "<br/>").replace(/\s{3}/g, '<span class="wbbtab"></span>');
1715                    }
1716
1717                }
1718            }
1719
1720            var postsel = "";
1721            this.seltextID = "wbbid_" + (++this.lastid);
1722            if (command != "link" && command != "img") {
1723                params["seltext"] = '<span id="' + this.seltextID + '">' + params["seltext"] + '</span>'; //use for select seltext
1724            } else {
1725                postsel = '<span id="' + this.seltextID + '">\uFEFF</span>'
1726            }
1727            var html = this.options.allButtons[command].html;
1728            html = html.replace(/\{(.*?)(\[.*?\])*\}/g, function (str, p, vrgx) {
1729                if (vrgx) {
1730                    var vrgxp = new RegExp(vrgx + "+", "i");
1731                    if (typeof(params[p.toLowerCase()]) != "undefined" && params[p.toLowerCase()].toString().match(vrgxp) === null) {
1732                        //not valid value
1733                        return "";
1734                    }
1735                }
1736                return (typeof(params[p.toLowerCase()]) == "undefined") ? "" : params[p.toLowerCase()];
1737            });
1738
1739            //insert first with max params
1740            var rhtml = null, maxpcount = 0;
1741            if (this.options.allButtons[command].transform) {
1742                var tr = [];
1743                $.each(this.options.allButtons[command].transform, function (html, bb) {
1744                    tr.push(html);
1745                });
1746                tr = this.sortArray(tr, -1);
1747                $.each(tr, function (i, v) {
1748                    var valid = true, pcount = 0, pname = {};
1749                    v = v.replace(/\{(.*?)(\[.*?\])*\}/g, function (str, p, vrgx) {
1750                        var vrgxp;
1751                        p = p.toLowerCase();
1752                        if (vrgx) {
1753                            vrgxp = new RegExp(vrgx + "+", "i");
1754                        }
1755                        if (typeof(params[p]) == "undefined" || (vrgx && params[p].toString().match(vrgxp) === null)) {
1756                            valid = false;
1757                        }
1758                        if (typeof(params[p]) != "undefined" && !pname[p]) {
1759                            pname[p] = 1;
1760                            pcount++;
1761                        }
1762                        return (typeof(params[p]) == "undefined") ? "" : params[p];
1763                    });
1764                    if (valid && (pcount > maxpcount)) {
1765                        rhtml = v;
1766                        maxpcount = pcount;
1767                    }
1768                });
1769            }
1770            return (rhtml || html) + postsel;
1771        },
1772
1773        //SELECTION FUNCTIONS
1774        getSelection: function () {
1775            if (window.getSelection) {
1776                return window.getSelection();
1777            } else if (document.selection) {
1778                return (this.options.bbmode) ? document.selection.createRange() : document.selection.createRange();
1779            }
1780        },
1781        getSelectText: function (fromTxtArea, range) {
1782            if (fromTxtArea) {
1783                //return select text from textarea
1784                this.txtArea.focus();
1785                if ('selectionStart' in this.txtArea) {
1786                    var l = this.txtArea.selectionEnd - this.txtArea.selectionStart;
1787                    return this.txtArea.value.substr(this.txtArea.selectionStart, l);
1788                } else {
1789                    //IE
1790                    var r = document.selection.createRange();
1791                    return r.text;
1792                }
1793            } else {
1794                //return select html from body
1795                this.body.focus();
1796                if (!range) {
1797                    range = this.getRange()
1798                }
1799                if (window.getSelection) {
1800                    //w3c
1801                    if (range) {
1802                        return $('<div>').append(range.cloneContents()).html();
1803                    }
1804                } else {
1805                    //ie
1806                    return range.htmlText;
1807                }
1808            }
1809            return "";
1810        },
1811        getRange: function () {
1812            if (window.getSelection) {
1813                var sel = this.getSelection();
1814                if (sel.getRangeAt && sel.rangeCount > 0) {
1815                    return sel.getRangeAt(0);
1816                } else if (sel.anchorNode) {
1817                    var range = (this.options.bbmode) ? document.createRange() : document.createRange();
1818                    range.setStart(sel.anchorNode, sel.anchorOffset);
1819                    range.setEnd(sel.focusNode, sel.focusOffset);
1820                    return range;
1821                }
1822            } else {
1823                return (this.options.bbmode === true) ? document.selection.createRange() : document.selection.createRange();
1824            }
1825        },
1826        insertAtCursor: function (code, forceBBMode) {
1827            if (typeof(code) != "string") {
1828                code = $("<div>").append(code).html();
1829            }
1830            if ((this.options.bbmode && typeof(forceBBMode) == "undefined") || forceBBMode === true) {
1831                var clbb = code.replace(/.*(\[\/\S+?\])$/, "$1");
1832                var p = this.getCursorPosBB() + ((code.indexOf(clbb) != -1 && code.match(/\[.*\]/)) ? code.indexOf(clbb) : code.length);
1833                if (document.selection) {
1834                    //IE
1835                    this.txtArea.focus();
1836                    this.getSelection().text = code;
1837                } else if (this.txtArea.selectionStart || this.txtArea.selectionStart == '0') {
1838                    this.txtArea.value = this.txtArea.value.substring(0, this.txtArea.selectionStart) + code + this.txtArea.value.substring(this.txtArea.selectionEnd, this.txtArea.value.length);
1839                }
1840                if (p < 0) {
1841                    p = 0;
1842                }
1843                this.setCursorPosBB(p);
1844            } else {
1845                this.execNativeCommand("insertHTML", code);
1846                var node = this.getSelectNode();
1847                if (!$(node).closest("table,tr,td")) {
1848                    this.splitPrevNext(node);
1849                }
1850            }
1851        },
1852        getSelectNode: function (rng) {
1853            this.body.focus();
1854            if (!rng) {
1855                rng = this.getRange();
1856            }
1857            if (!rng) {
1858                return this.$body;
1859            }
1860            //return (window.getSelection) ? rng.commonAncestorContainer:rng.parentElement();
1861            var sn = (window.getSelection) ? rng.commonAncestorContainer : rng.parentElement();
1862            if ($(sn).is(".imgWrap")) {
1863                sn = $(sn).children("img")[0];
1864            }
1865            return sn;
1866        },
1867        getCursorPosBB: function () {
1868            var pos = 0;
1869            if ('selectionStart' in this.txtArea) {
1870                pos = this.txtArea.selectionStart;
1871            } else {
1872                this.txtArea.focus();
1873                var r = this.getRange();
1874                var rt = document.body.createTextRange();
1875                rt.moveToElementText(this.txtArea);
1876                rt.setEndPoint('EndToStart', r);
1877                pos = rt.text.length;
1878            }
1879            return pos;
1880        },
1881        setCursorPosBB: function (pos) {
1882            if (this.options.bbmode) {
1883                if (window.getSelection) {
1884                    this.txtArea.selectionStart = pos;
1885                    this.txtArea.selectionEnd = pos;
1886                } else {
1887                    var range = this.txtArea.createTextRange();
1888                    range.collapse(true);
1889                    range.move('character', pos);
1890                    range.select();
1891                }
1892            }
1893        },
1894        selectNode: function (node, rng) {
1895            if (!rng) {
1896                rng = this.getRange();
1897            }
1898            if (!rng) {
1899                return;
1900            }
1901            if (window.getSelection) {
1902                var sel = this.getSelection();
1903                rng.selectNodeContents(node);
1904                sel.removeAllRanges();
1905                sel.addRange(rng);
1906            } else {
1907                rng.moveToElementText(node);
1908                rng.select();
1909            }
1910        },
1911        selectRange: function (rng) {
1912            if (rng) {
1913                if (!window.getSelection) {
1914                    rng.select();
1915                } else {
1916                    var sel = this.getSelection();
1917                    sel.removeAllRanges();
1918                    sel.addRange(rng);
1919                }
1920            }
1921        },
1922        cloneRange: function (rng) {
1923            if (rng) {
1924                if (!window.getSelection) {
1925                    return rng.duplicate();
1926                } else {
1927                    return rng.cloneRange();
1928                }
1929            }
1930        },
1931        getRangeClone: function () {
1932            return this.cloneRange(this.getRange());
1933        },
1934        saveRange: function () {
1935            this.setBodyFocus();
1936            //this.lastRange=(this.options.bbmode) ? this.getCursorPosBB():this.getRangeClone();
1937            this.lastRange = this.getRangeClone();
1938        },
1939        selectLastRange: function () {
1940            if (this.lastRange) {
1941                this.body.focus();
1942                this.selectRange(this.lastRange);
1943                this.lastRange = false;
1944            }
1945        },
1946        setBodyFocus: function () {
1947            $.log("Set focus to WysiBB editor");
1948            if (this.options.bbmode) {
1949                if (!this.$txtArea.is(":focus")) {
1950                    this.$txtArea.focus();
1951                }
1952            } else {
1953                if (!this.$body.is(":focus")) {
1954                    this.$body.focus();
1955                }
1956            }
1957        },
1958        clearLastRange: function () {
1959            this.lastRange = false;
1960        },
1961
1962        //TRANSFORM FUNCTIONS
1963        filterByNode: function (node) {
1964            var $n = $(node);
1965            var tagName = $n.get(0).tagName.toLowerCase();
1966            var filter = tagName;
1967            var attributes = this.getAttributeList($n.get(0));
1968            $.each(attributes, $.proxy(function (i, item) {
1969                var v = $n.attr(item);
1970                /* $.log("v: "+v);
1971                 if ($.inArray(item,this.options.attrWrap)!=-1) {
1972                 item = '_'+item;
1973                 } */
1974                //$.log(item);
1975                if (item.substr(0, 1) == "_") {
1976                    item = item.substr(1, item.length)
1977                }
1978                if (v && !v.match(/\{.*?\}/)) {
1979                    //$.log("I1: "+item);
1980                    if (item == "style") {
1981                        var v = $n.attr(item);
1982                        var va = v.split(";");
1983                        $.each(va, function (i, f) {
1984                            if (f && f.length > 0) {
1985                                filter += '[' + item + '*="' + $.trim(f) + '"]';
1986                            }
1987                        });
1988                    } else {
1989                        filter += '[' + item + '="' + v + '"]';
1990                    }
1991                } else if (v && item == "style") {
1992                    //$.log("I2: "+item);
1993                    var vf = v.substr(0, v.indexOf("{"));
1994                    if (vf && vf != "") {
1995                        var v = v.substr(0, v.indexOf("{"));
1996                        var va = v.split(";");
1997                        $.each(va, function (i, f) {
1998                            filter += '[' + item + '*="' + f + '"]';
1999                        });
2000                        //filter+='['+item+'*="'+v.substr(0,v.indexOf("{"))+'"]';
2001                    }
2002                } else { //1.2.2
2003                    //$.log("I3: "+item);
2004                    filter += '[' + item + ']';
2005                }
2006            }, this));
2007
2008            //index
2009            var idx = $n.parent().children(filter).index($n);
2010            if (idx > 0) {
2011                filter += ":eq(" + $n.index() + ")";
2012            }
2013            return filter;
2014        },
2015        relFilterByNode: function (node, stop) {
2016            var p = "";
2017            $.each(this.options.attrWrap, function (i, a) {
2018                stop = stop.replace('[' + a, '[_' + a);
2019            });
2020            while (node && node.tagName != "BODY" && !$(node).is(stop)) {
2021                p = this.filterByNode(node) + " " + p;
2022                if (node) {
2023                    node = node.parentNode;
2024                }
2025            }
2026            return p;
2027        },
2028        getRegexpReplace: function (str, validname) {
2029            str = str.replace(/(\(|\)|\[|\]|\.|\*|\?|\:|\\)/g, "\\$1")
2030                .replace(/\s+/g, "\\s+")
2031                .replace(validname.replace(/(\(|\)|\[|\]|\.|\*|\?|\:|\\)/g, "\\$1"), "(.+)")
2032                .replace(/\{\S+?\}/g, ".*");
2033            return (str);
2034        },
2035        getBBCode: function () {
2036            if (!this.options.rules) {
2037                return this.$txtArea.val();
2038            }
2039            if (this.options.bbmode) {
2040                return this.$txtArea.val();
2041            }
2042            this.clearEmpty();
2043            this.removeLastBodyBR();
2044            return this.toBB(this.$body.html());
2045        },
2046        toBB: function (data) {
2047            if (!data) {
2048                return "";
2049            }
2050            var $e = (typeof(data) == "string") ? $('<span>').html(data) : $(data);
2051            //remove last BR
2052            $e.find("div,blockquote,p").each(function () {
2053                if (this.nodeType != 3 && this.lastChild && this.lastChild.tagName == "BR") {
2054                    $(this.lastChild).remove();
2055                }
2056            });
2057            if ($e.is("div,blockquote,p") && $e[0].nodeType != 3 && $e[0].lastChild && $e[0].lastChild.tagName == "BR") {
2058                $($e[0].lastChild).remove();
2059            }
2060            //END remove last BR
2061
2062            //Remove BR
2063            $e.find("ul > br, table > br, tr > br").remove();
2064            //IE
2065
2066            var outbb = "";
2067
2068            //transform smiles
2069            $.each(this.options.srules, $.proxy(function (s, bb) {
2070                $e.find(s).replaceWith(bb[0]);
2071            }, this));
2072
2073            $e.contents().each($.proxy(function (i, el) {
2074                var $el = $(el);
2075                if (el.nodeType === 3) {
2076                    outbb += el.data.replace(/\n+/, "").replace(/\t/g, "   ");
2077                } else {
2078                    //process html tag
2079                    var rpl, processed = false;
2080
2081                    //for (var rootsel in this.options.rules) {
2082                    for (var j = 0; j < this.rsellist.length; j++) {
2083                        var rootsel = this.rsellist[j];
2084                        if ($el && $el.is(rootsel)) {
2085                            //it is root sel
2086                            var rlist = this.options.rules[rootsel];
2087                            for (var i = 0; i < rlist.length; i++) {
2088                                var bbcode = rlist[i][0];
2089                                var crules = rlist[i][1];
2090                                var skip = false, keepElement = false, keepAttr = false;
2091                                if (!$el.is("br")) {
2092                                    bbcode = bbcode.replace(/\n/g, "<br>");
2093                                }
2094                                bbcode = bbcode.replace(/\{(.*?)(\[.*?\])*\}/g, $.proxy(function (str, s, vrgx) {
2095                                    var c = crules[s.toLowerCase()];
2096                                    //if (typeof(c)=="undefined") {$.log("Param: {"+s+"} not found in HTML representation.");skip=true;return s;}
2097                                    if (typeof(c) == "undefined") {
2098                                        $.log("Param: {" + s + "} not found in HTML representation.");
2099                                        skip = true;
2100                                    }
2101                                    var $cel = (c.sel) ? $(el).find(c.sel) : $(el);
2102                                    if (c.attr && !$cel.attr(c.attr)) {
2103                                        skip = true;
2104                                        return s;
2105                                    } //skip if needed attribute not present, maybe other bbcode
2106                                    var cont = (c.attr) ? $cel.attr(c.attr) : $cel.html();
2107                                    if (typeof(cont) == "undefined" || cont == null) {
2108                                        skip = true;
2109                                        return s;
2110                                    }
2111                                    var regexp = c.rgx;
2112
2113                                    //style fix 
2114                                    if (regexp && c.attr == "style" && regexp.substr(regexp.length - 1, 1) != ";") {
2115                                        regexp += ";";
2116                                    }
2117                                    if (c.attr == "style" && cont && cont.substr(cont.length - 1, 1) != ";") {
2118                                        cont += ";"
2119                                    }
2120                                    //prepare regexp
2121                                    var rgx = (regexp) ? new RegExp(regexp, "") : false;
2122                                    if (rgx) {
2123                                        if (cont.match(rgx)) {
2124                                            var m = cont.match(rgx);
2125                                            if (m && m.length == 2) {
2126                                                cont = m[1];
2127                                            }
2128                                        } else {
2129                                            cont = "";
2130                                        }
2131                                    }
2132
2133                                    //if it is style attr, then keep tag alive, remove this style
2134                                    if (c.attr && skip === false) {
2135                                        if (c.attr == "style") {
2136                                            keepElement = true;
2137                                            var nstyle = "";
2138                                            var r = c.rgx.replace(/^\.\*\?/, "").replace(/\.\*$/, "").replace(/;$/, "");
2139                                            $($cel.attr("style").split(";")).each(function (idx, style) {
2140                                                if (style && style != "") {
2141                                                    if (!style.match(r)) {
2142                                                        nstyle += style + ";";
2143                                                    }
2144                                                }
2145                                            });
2146                                            if (nstyle == "") {
2147                                                $cel.removeAttr("style");
2148                                            } else {
2149                                                $cel.attr("style", nstyle);
2150                                            }
2151                                        } else if (c.rgx === false) {
2152                                            keepElement = true;
2153                                            keepAttr = true;
2154                                            $cel.removeAttr(c.attr);
2155                                        }
2156                                    }
2157                                    if ($el.is('table,tr,td,font')) {
2158                                        keepElement = true;
2159                                    }
2160
2161                                    return cont || "";
2162                                }, this));
2163                                if (skip) {
2164                                    continue;
2165                                }
2166                                if ($el.is("img,br,hr")) {
2167                                    //replace element
2168                                    outbb += bbcode;
2169                                    $el = null;
2170                                    break;
2171                                } else {
2172                                    if (keepElement && !$el.attr("notkeep")) {
2173                                        if ($el.is("table,tr,td")) {
2174                                            bbcode = this.fixTableTransform(bbcode);
2175                                            outbb += this.toBB($('<span>').html(bbcode));
2176                                            $el = null;
2177                                        } else {
2178                                            $el.empty().html('<span>' + bbcode + '</span>');
2179                                        }
2180
2181                                    } else {
2182                                        if ($el.is("iframe")) {
2183                                            outbb += bbcode;
2184                                        } else {
2185                                            $el.empty().html(bbcode);
2186                                            outbb += this.toBB($el);
2187                                            $el = null;
2188
2189                                        }
2190                                        break;
2191                                    }
2192                                }
2193                            }
2194                        }
2195                    }
2196                    if (!$el || $el.is("iframe,img")) {
2197                        return true;
2198                    }
2199                    outbb += this.toBB($el);
2200                }
2201            }, this));
2202
2203            outbb.replace(/\uFEFF/g, "");
2204            return outbb;
2205        },
2206        getHTML: function (bbdata, init, skiplt) {
2207            if (!this.options.bbmode && !init) {
2208                return this.$body.html()
2209            }
2210
2211            if (!skiplt) {
2212                bbdata = bbdata.replace(/</g, "&lt;").replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
2213            }
2214            bbdata = bbdata.replace(/\[code\]([\s\S]*?)\[\/code\]/g, function (s) {
2215                s = s.substr("[code]".length, s.length - "[code]".length - "[/code]".length).replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
2216                return "[code]" + s + "[/code]";
2217            });
2218
2219
2220            $.each(this.options.btnlist, $.proxy(function (i, b) {
2221                if (b != "|" && b != "-") {
2222                    var find = true;
2223                    if (!this.options.allButtons[b] || !this.options.allButtons[b].transform) {
2224                        return true;
2225                    }
2226
2227                    $.each(this.options.allButtons[b].transform, $.proxy(function (html, bb) {
2228                        html = html.replace(/\n/g, ""); //IE 7,8 FIX
2229                        var a = [];
2230                        bb = bb.replace(/(\(|\)|\[|\]|\.|\*|\?|\:|\\|\\)/g, "\\$1");
2231                        //.replace(/\s/g,"\\s");
2232                        bb = bb.replace(/\{(.*?)(\\\[.*?\\\])*\}/gi, $.proxy(function (str, s, vrgx) {
2233                            a.push(s);
2234                            if (vrgx) {
2235                                //has validation regexp
2236                                vrgx = vrgx.replace(/\\/g, "");
2237                                return "(" + vrgx + "*?)";
2238                            }
2239                            return "([\\s\\S]*?)";
2240                        }, this));
2241                        var n = 0, am;
2242                        while ((am = (new RegExp(bb, "mgi")).exec(bbdata)) != null) {
2243                            if (am) {
2244                                var r = {};
2245                                $.each(a, $.proxy(function (i, k) {
2246                                    r[k] = am[i + 1];
2247                                }, this));
2248                                var nhtml = html;
2249                                nhtml = nhtml.replace(/\{(.*?)(\[.*?\])\}/g, "{$1}");
2250                                nhtml = this.strf(nhtml, r);
2251                                bbdata = bbdata.replace(am[0], nhtml);
2252                            }
2253                        }
2254                    }, this));
2255                }
2256            }, this));
2257
2258            //transform system codes
2259            $.each(this.options.systr, function (html, bb) {
2260                bb = bb.replace(/(\(|\)|\[|\]|\.|\*|\?|\:|\\|\\)/g, "\\$1")
2261                    .replace(" ", "\\s");
2262                bbdata = bbdata.replace(new RegExp(bb, "g"), html);
2263            });
2264
2265
2266            var $wrap = $(this.elFromString("<div>" + bbdata + "</div>"));
2267            //transform smiles
2268            /* $wrap.contents().filter(function() {return this.nodeType==3}).each($.proxy(smilerpl,this)).end().find("*").contents().filter(function() {return this.nodeType==3}).each($.proxy(smilerpl,this));
2269
2270             function smilerpl(i,el) {
2271             var ndata = el.data;
2272             $.each(this.options.smileList,$.proxy(function(i,row) {
2273             var fidx = ndata.indexOf(row.bbcode);
2274             if (fidx!=-1) {
2275             var afternode_txt = ndata.substring(fidx+row.bbcode.length,ndata.length);
2276             var afternode = document.createTextNode(afternode_txt);
2277             el.data = ndata = el.data.substr(0,fidx);
2278             $(el).after(afternode).after(this.strf(row.img,this.options));
2279             }
2280             },this));	
2281             } */
2282            this.getHTMLSmiles($wrap);
2283            //$wrap.contents().filter(function() {return this.nodeType==3}).each($.proxy(this,smileRPL,this));
2284
2285            return $wrap.html();
2286        },
2287        getHTMLSmiles: function (rel) {
2288            $(rel).contents().filter(function () {
2289                return this.nodeType == 3
2290            }).each($.proxy(this.smileRPL, this));
2291        },
2292        smileRPL: function (i, el) {
2293            var ndata = el.data;
2294            $.each(this.options.smileList, $.proxy(function (i, row) {
2295                var fidx = ndata.indexOf(row.bbcode);
2296                if (fidx != -1) {
2297                    var afternode_txt = ndata.substring(fidx + row.bbcode.length, ndata.length);
2298                    var afternode = document.createTextNode(afternode_txt);
2299                    el.data = ndata = el.data.substr(0, fidx);
2300                    $(el).after(afternode).after(this.strf(row.img, this.options));
2301                    this.getHTMLSmiles(el.parentNode);
2302                    return false;
2303                }
2304                this.getHTMLSmiles(el);
2305            }, this));
2306        },
2307        //UTILS
2308        setUID: function (el, attr) {
2309            var id = "wbbid_" + (++this.lastid);
2310            if (el) {
2311                $(el).attr(attr || "id", id);
2312            }
2313            return id;
2314        },
2315        keysToLower: function (o) {
2316            $.each(o, function (k, v) {
2317                if (k != k.toLowerCase()) {
2318                    delete o[k];
2319                    o[k.toLowerCase()] = v;
2320                }
2321            });
2322            return o;
2323        },
2324        strf: function (str, data) {
2325            data = this.keysToLower($.extend({}, data));
2326            return str.replace(/\{([\w\.]*)\}/g, function (str, key) {
2327                key = key.toLowerCase();
2328                var keys = key.split("."), value = data[keys.shift().toLowerCase()];
2329                $.each(keys, function () {
2330                    value = value[this];
2331                });
2332                return (value === null || value === undefined) ? "" : value;
2333            });
2334        },
2335        elFromString: function (str) {
2336            if (str.indexOf("<") != -1 && str.indexOf(">") != -1) {
2337                //create tag
2338                var wr = document.createElement("SPAN");
2339                $(wr).html(str);
2340                this.setUID(wr, "wbb");
2341                return ($(wr).contents().size() > 1) ? wr : wr.firstChild;
2342            } else {
2343                //create text node
2344                return document.createTextNode(str);
2345            }
2346        },
2347        isContain: function (node, sel) {
2348            while (node && !$(node).hasClass("wysibb")) {
2349                if ($(node).is(sel)) {
2350                    return node
2351                }
2352                if (node) {
2353                    node = node.parentNode;
2354                }
2355                else {
2356                    return null;
2357                }
2358            }
2359        },
2360        isBBContain: function (bbcode) {
2361            var pos = this.getCursorPosBB();
2362            var b = this.prepareRGX(bbcode);
2363            var bbrgx = new RegExp(b, "g");
2364            var a;
2365            var lastindex = 0;
2366            while ((a = bbrgx.exec(this.txtArea.value)) != null) {
2367                var p = this.txtArea.value.indexOf(a[0], lastindex);
2368                if (pos > p && pos < (p + a[0].length)) {
2369                    return [a, p];
2370                }
2371                lastindex = p + 1;
2372            }
2373        },
2374        prepareRGX: function (r) {
2375            return r.replace(/(\[|\]|\)|\(|\.|\*|\?|\:|\||\\)/g, "\\$1").replace(/\{.*?\}/g, "([\\s\\S]*?)");
2376            //return r.replace(/([^a-z0-9)/ig,"\\$1").replace(/\{.*?\}/g,"([\\s\\S]*?)");
2377        },
2378        checkForLastBR: function (node) {
2379            if (!node) {
2380                $node = this.body;
2381            }
2382            if (node.nodeType == 3) {
2383                node = node.parentNode;
2384            }
2385            var $node = $(node);
2386            if ($node.is("span[id*='wbbid']")) {
2387                $node = $node.parent();
2388            }
2389            if (this.options.bbmode === false && $node.is('div,blockquote,code') && $node.contents().size() > 0) {
2390                var l = $node[0].lastChild;
2391                if (!l || (l && l.tagName != "BR")) {
2392                    $node.append("<br/>");
2393                }
2394            }
2395            if (this.$body.contents().size() > 0 && this.body.lastChild.tagName != "BR") {
2396                this.$body.append('<br/>');
2397            }
2398        },
2399        getAttributeList: function (el) {
2400            var a = [];
2401            $.each(el.attributes, function (i, attr) {
2402                if (attr.specified) {
2403                    a.push(attr.name);
2404                }
2405            });
2406            return a;
2407        },
2408        clearFromSubInsert: function (html, cmd) {
2409            if (this.options.allButtons[cmd] && this.options.allButtons[cmd].rootSelector) {
2410                var $wr = $('<div>').html(html);
2411                $.each(this.options.allButtons[cmd].rootSelector, $.proxy(function (i, s) {
2412                    var seltext = false;
2413                    if (typeof(this.options.rules[s][0][1]["seltext"]) != "undefined") {
2414                        seltext = this.options.rules[s][0][1]["seltext"]["sel"];
2415                    }
2416                    var res = true;
2417                    $wr.find("*").each(function () { //work with find("*") and "is", becouse in 
2417ie7-8 find is case sensitive
2418                        if ($(this).is(s)) {
2419                            if (seltext && seltext["sel"]) {
2420                                $(this).replaceWith($(this).find(seltext["sel"].toLowerCase()).html());
2421                            } else {
2422                                $(this).replaceWith($(this).html());
2423                            }
2424                            res = false;
2425                        }
2426                    });
2427                    return res;
2428                }, this));
2429                return $wr.html();
2430            }
2431            return html;
2432        },
2433        splitPrevNext: function (node) {
2434            if (node.nodeType == 3) {
2435                node = node.parentNode
2436            }
2437            var f = this.filterByNode(node).replace(/\:eq.*$/g, "");
2438            if ($(node.nextSibling).is(f)) {
2439                $(node).append($(node.nextSibling).html());
2440                $(node.nextSibling).remove();
2441            }
2442            if ($(node.previousSibling).is(f)) {
2443                $(node).prepend($(node.previousSibling).html());
2444                $(node.previousSibling).remove();
2445            }
2446        },
2447        modeSwitch: function () {
2448            if (this.options.bbmode) {
2449                //to HTML
2450                this.$body.html(this.getHTML(this.$txtArea.val()));
2451                this.$txtArea.hide().removeAttr("wbbsync").val("");
2452                this.$body.css("min-height", this.$txtArea.height()).show().focus();
2453            } else {
2454                //to bbcode
2455                this.$txtArea.val(this.getBBCode()).css("min-height", this.$body.height());
2456                this.$body.hide();
2457                this.$txtArea.show().focus();
2458            }
2459            this.options.bbmode = !this.options.bbmode;
2460        },
2461        clearEmpty: function () {
2462            this.$body.children().filter(emptyFilter).remove();
2463            function emptyFilter() {
2464                if (!$(this).is("span,font,a,b,i,u,s")) {
2465                    //clear empty only for span,font
2466                    return false;
2467                }
2468                if (!$(this).hasClass("wbbtab") && $.trim($(this).html()).length == 0) {
2469                    return true;
2470                } else if ($(this).children().size() > 0) {
2471                    $(this).children().filter(emptyFilter).remove();
2472                    if ($(this).html().length == 0 && this.tagName != "BODY") {
2473                        return true;
2474                    }
2475                }
2476            }
2477        },
2478        dropdownclick: function (bsel, tsel, e) {
2479            //this.body.focus();
2480            var $btn = $(e.currentTarget).closest(bsel);
2481            if ($btn.hasClass("dis")) {
2482                return;
2483            }
2484            if ($btn.attr("wbbshow")) {
2485                //hide dropdown
2486                $btn.removeAttr("wbbshow");
2487                $(document).unbind("mousedown", this.dropdownhandler);
2488                if (document) {
2489                    $(document).unbind("mousedown", this.dropdownhandler);
2490                }
2491                this.lastRange = false;
2492
2493            } else {
2494                this.saveRange();
2495                this.$editor.find("*[wbbshow]").each(function (i, el) {
2496                    $(el).removeClass("on").find($(el).attr("wbbshow")).hide().end().removeAttr("wbbshow");
2497                });
2498                $btn.attr("wbbshow", tsel);
2499                $(document.body).bind("mousedown", $.proxy(function (evt) {
2500                    this.dropdownhandler($btn, bsel, tsel, evt)
2501                }, this));
2502                if (this.$body) {
2503                    this.$body.bind("mousedown", $.proxy(function (evt) {
2504                        this.dropdownhandler($btn, bsel, tsel, evt)
2505                    }, this));
2506                }
2507            }
2508            $btn.find(tsel).toggle();
2509            $btn.toggleClass("on");
2510        },
2511        dropdownhandler: function ($btn, bsel, tsel, e) {
2512            if ($(e.target).parents(bsel).size() == 0) {
2513                $btn.removeClass("on").find(tsel).hide();
2514                $(document).unbind('mousedown', this.dropdownhandler);
2515                if (this.$body) {
2516                    this.$body.unbind('mousedown', this.dropdownhandler);
2517                }
2518            }
2519        },
2520        rgbToHex: function (rgb) {
2521            if (rgb.substr(0, 1) == '#') {
2522                return rgb;
2523            }
2524            //if (rgb.indexOf("rgb")==-1) {return rgb;}
2525            if (rgb.indexOf("rgb") == -1) {
2526                //IE
2527                var color = parseInt(rgb);
2528                color = ((color & 0x0000ff) << 16) | (color & 0x00ff00) | ((color & 0xff0000) >>> 16);
2529                return '#' + color.toString(16);
2530            }
2531            var digits = /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(rgb);
2532            return "#" + this.dec2hex(parseInt(digits[2])) + this.dec2hex(parseInt(digits[3])) + this.dec2hex(parseInt(digits[4]));
2533        },
2534        dec2hex: function (d) {
2535            if (d > 15) {
2536                return d.toString(16);
2537            } else {
2538                return "0" + d.toString(16);
2539            }
2540        },
2541        sync: function () {
2542            if (this.options.bbmode) {
2543                this.$body.html(this.getHTML(this.txtArea.value, true));
2544            } else {
2545                this.$txtArea.attr("wbbsync", 1).val(this.getBBCode());
2546            }
2547        },
2548        clearPaste: function (el) {
2549            var $block = $(el);
2550            //NEW 
2551            $.each(this.options.rules, $.proxy(function (s, ar) {
2552                var $sf = $block.find(s).attr("wbbkeep", 1);
2553                if ($sf.size() > 0) {
2554                    var s2 = ar[0][1];
2555                    $.each(s2, function (i, v) {
2556                        if (v.sel) {
2557                            $sf.find(v.sel).attr("wbbkeep", 1);
2558                        }
2559                    });
2560                }
2561            }, this));
2562            $block.find("*[wbbkeep!='1']").each($.proxy(function (i, el) {
2563                var $this = $(el);
2564                if ($this.is('div,p') && ($this.children().size() == 0 || el.lastChild.tagName != "BR")) {
2565                    $this.after("<br/>");
2566                }
2567            }, this));
2568            $block.find("*[wbbkeep]").removeAttr("wbbkeep").removeAttr("style");
2569            $.log($block.html());
2570            //$.log("BBCODE: "+this.toBB($block.clone(true)));
2571            $block.html(this.getHTML(this.toBB($block), true));
2572            $.log($block.html());
2573
2574            //OLD
2575            /* $.each(this.options.rules,$.proxy(function(s,bb) {
2576             $block.find(s).attr("wbbkeep",1);
2577             },this));
2578
2579             //replace div and p without last br to html()+br
2580             $block.find("*[wbbkeep!='1']").each($.proxy(function(i,el) {
2581             var $this = $(el);
2582             if ($this.is('div,p') && ($this.children().size()==0 || el.lastChild.tagName!="BR")) {
2583             $this.after("<br/>").after($this.contents()).remove();
2584             }else{
2585             $this.after($this.contents()).remove();
2586             }
2587             },this));
2588             $block.find("*[wbbkeep]").removeAttr("wbbkeep").removeAttr("style"); */
2589        },
2590        sortArray: function (ar, asc) {
2591            ar.sort(function (a, b) {
2592                return (a.length - b.length) * (asc || 1);
2593            });
2594            return ar;
2595        },
2596        smileFind: function () {
2597            if (this.options.smilefind) {
2598                var $smlist = $(this.options.smilefind).find('img[alt]');
2599                if ($smlist.size() > 0) {
2600                    this.options.smileList = [];
2601                    $smlist.each($.proxy(function (i, el) {
2602                        var $el = $(el);
2603                        this.options.smileList.push({title: $el.attr("title"), bbcode: $el.attr("alt"), img: $el.removeAttr("alt").removeAttr("title")[0].outerHTML});
2604                    }, this));
2605                }
2606            }
2607        },
2608        destroy: function () {
2609            this.$editor.replaceWith(this.$txtArea);
2610            this.$txtArea.removeClass("wysibb-texarea").show();
2611            this.$modal.remove();
2612            this.$txtArea.data("wbb", null);
2613        },
2614        pressTab: function (e) {
2615            if (e && e.which == 9) {
2616                //insert tab
2617                if (e.preventDefault) {
2618                    e.preventDefault();
2619                }
2620                if (this.options.bbmode) {
2621                    this.insertAtCursor('   ', false);
2622                } else {
2623                    this.insertAtCursor('<span class="wbbtab">\uFEFF</span>', false);
2624                    //this.execNativeCommand("indent",false); 
2625                }
2626            }
2627        },
2628        removeLastBodyBR: function () {
2629            if (this.body.lastChild && this.body.lastChild.nodeType != 3 && this.body.lastChild.tagName == "BR") {
2630                this.body.removeChild(this.body.lastChild);
2631                this.removeLastBodyBR();
2632            }
2633        },
2634        traceTextareaEvent: function (e) {
2635            if ($(e.target).closest("div.wysibb").size() == 0) {
2636                if ($(document.activeElement).is("div.wysibb-body")) {
2637                    this.saveRange();
2638                }
2639                setTimeout($.proxy(function () {
2640                    var data = this.$txtArea.val();
2641                    if (this.options.bbmode === false && data != "" && $(e.target).closest("div.wysibb").size() == 0 && !this.$txtArea.attr("wbbsync")) {
2642                        this.selectLastRange();
2643                        this.insertAtCursor(this.getHTML(data, true));
2644                        this.$txtArea.val("");
2645                    }
2646                    if ($(document.activeElement).is("div.wysibb-body")) {
2647                        this.lastRange = false;
2648                    }
2649                }, this), 100);
2650            }
2651        },
2652        txtAreaInitContent: function () {
2653            //$.log(this.txtArea.value);
2654            this.$body.html(this.getHTML(this.txtArea.value, true));
2655        },
2656        getValidationRGX: function (s) {
2657            if (s.match(/\[\S+\]/)) {
2658                return s.replace(/.*(\\*\[\S+\]).*/, "$1");
2659            }
2660            return "";
2661        },
2662        smileConversion: function () {
2663            if (this.options.smileList && this.options.smileList.length > 0) {
2664                var snode = this.getSelectNode();
2665                if (snode.nodeType == 3) {
2666                    var ndata = snode.data;
2667                    if (ndata.length >= 2 && !this.isInClearTextBlock(snode) && $(snode).parents("a").size() == 0) {
2668                        $.each(this.options.srules, $.proxy(function (i, sar) {
2669                            var smbb = sar[0];
2670                            var fidx = ndata.indexOf(smbb);
2671                            if (fidx != -1) {
2672                                var afternode_txt = ndata.substring(fidx + smbb.length, ndata.length);
2673                                var afternode = document.createTextNode(afternode_txt);
2674                                var afternode_cursor = document.createElement("SPAN");
2675                                snode.data = snode.data.substr(0, fidx);
2676                                $(snode).after(afternode).after(afternode_cursor).after(this.strf(sar[1], this.options));
2677                                this.selectNode(afternode_cursor);
2678                                return false;
2679                            }
2680                        }, this));
2681                    }
2682                }
2683            }
2684        },
2685        isInClearTextBlock: function () {
2686            if (this.cleartext) {
2687                var find = false;
2688                $.each(this.cleartext, $.proxy(function (sel, command) {
2689                    if (this.queryState(command)) {
2690                        find = command;
2691                        return false;
2692                    }
2693                }, this));
2694                return find;
2695            }
2696            return false;
2697        },
2698        wrapAttrs: function (html) {
2699            $.each(this.options.attrWrap, function (i, a) {
2700                html = html.replace(a + '="', '_' + a + '="');
2701            });
2702            return html;
2703        },
2704        unwrapAttrs: function (html) {
2705            $.each(this.options.attrWrap, function (i, a) {
2706                html = html.replace('_' + a + '="', a + '="');
2707            });
2708            return html;
2709        },
2710        disNonActiveButtons: function () {
2711            if (this.isInClearTextBlock()) {
2712                this.$toolbar.find(".wysibb-toolbar-btn:not(.on,.mswitch)").addClass("dis");
2713            } else {
2714                this.$toolbar.find(".wysibb-toolbar-btn.dis").removeClass("dis");
2715            }
2716        },
2717        setCursorByEl: function (el) {
2718            var sl = document.createTextNode("\uFEFF");
2719            $(el).after(sl);
2720            this.selectNode(sl);
2721        },
2722
2723        //img listeners
2724        imgListeners: function () {
2725            $(document).on("mousedown", $.proxy(this.imgEventHandler, this));
2726        },
2727        imgEventHandler: function (e) {
2728            var $e = $(e.target);
2729            if (this.hasWrapedImage && ($e.closest(".wbb-img,#wbbmodal").size() == 0 || $e.hasClass("wbb-cancel-button"))) {
2730                this.$body.find(".imgWrap ").each(function () {
2731                    $.log("Removed imgWrap block");
2732                    $(this).replaceWith($(this).find("img"));
2733                });
2734                this.hasWrapedImage = false;
2735                this.updateUI();
2736            }
2737
2738            if ($e.is("img") && $e.closest(".wysibb-body").size() > 0) {
2739                $e.wrap("<span class='imgWrap'></span>");
2740                this.hasWrapedImage = $e;
2741                this.$body.focus();
2742                this.selectNode($e.parent()[0]);
2743            }
2744        },
2745
2746        //MODAL WINDOW
2747        showModal: function (cmd, opt, queryState) {
2748            $.log("showModal: " + cmd);
2749            this.saveRange();
2750            var $cont = this.$modal.find(".wbbm-content").html("");
2751            var $wbbm = this.$modal.find(".wbbm").removeClass("hastabs");
2752            this.$modal.find("span.wbbm-title-text").html(opt.title);
2753            if (opt.tabs && opt.tabs.length > 1) {
2754                //has tabs, create
2755                $wbbm.addClass("hastabs");
2756                var $ul = $('<div class="wbbm-tablist">').appendTo($cont).append("<ul>").children("ul");
2757                $.each(opt.tabs, $.proxy(function (i, row) {
2758                    if (i == 0) {
2759                        row['on'] = "on"
2760                    }
2761                    $ul.append(this.strf('<li class="{on}" onClick="$(this).parent().find(\'.on\').removeClass(\'on\');$(this).addClass(\'on\');$(this).parents(\'.wbbm-content\').find(\'.tab-cont\').hide();$(this).parents(\'.wbbm-content\').find(\'.tab' + i + '\').show()">{title}</li>', row));
2762
2763                }, this))
2764            }
2765            if (opt.width) {
2766                $wbbm.css("width", opt.width);
2767            }
2768            var $cnt = $('<div class="wbbm-cont">').appendTo($cont);
2769            if (queryState) {
2770                $wbbm.find('#wbbm-remove').show();
2771            } else {
2772                $wbbm.find('#wbbm-remove').hide();
2773            }
2774            $.each(opt.tabs, $.proxy(function (i, r) {
2775                var $c = $('<div>').addClass("tab-cont tab" + i).attr("tid", i).appendTo($cnt);
2776                if (i > 0) {
2777                    $c.hide();
2778                }
2779                if (r.html) {
2780                    $c.html(this.strf(r.html, this.options));
2781                } else {
2782                    $.each(r.input, $.proxy(function (j, inp) {
2783                        inp["value"] = queryState[inp.param.toLowerCase()];
2784                        if (inp.param.toLowerCase() == "seltext" && (!inp["value"] || inp["value"] == "")) {
2785                            inp["value"] = this.getSelectText(this.options.bbmode);
2786                        }
2787                        if (inp["value"] && inp["value"].indexOf("<span id='wbbid") == 0 && $(inp["value"]).is("span[id*='wbbid']")) {
2788                            inp["value"] = $(inp["value"]).html();
2789                        }
2790                        if (inp.type && inp.type == "div") {
2791                            //div input, support wysiwyg input
2792                            $c.append(this.strf('<div class="wbbm-inp-row"><label>{title}</label><div class="inp-text div-modal-text" contenteditable="true" name="{param}">{value}</div></div>', inp));
2793                        } else {
2794                            //default input
2795                            $c.append(this.strf('<div class="wbbm-inp-row"><label>{title}</label><input class="inp-text modal-text" type="text" name="{param}" value="{value}"/></div>', inp));
2796                        }
2797
2798
2799                    }, this));
2800                }
2801            }, this));
2802
2803            //this.lastRange=this.getRange();
2804
2805            if ($.isFunction(opt.onLoad)) {
2806                opt.onLoad.call(this, cmd, opt, queryState);
2807            }
2808
2809            $wbbm.find('#wbbm-submit').click($.proxy(function () {
2810
2811                if ($.isFunction(opt.onSubmit)) { //custom submit function, if return false, then don't process our function
2812                    var r = opt.onSubmit.call(this, cmd, opt, queryState);
2813                    if (r === false) {
2814                        return;
2815                    }
2816                }
2817                var params = {};
2818                var valid = true;
2819                this.$modal.find(".wbbm-inperr").remove();
2820                this.$modal.find(".wbbm-brdred").removeClass("wbbm-brdred");
2821                //$.each(this.$modal.find(".tab-cont:visible input"),$.proxy(function(i,el) {
2822                $.each(this.$modal.find(".tab-cont:visible .inp-text"), $.proxy(function (i, el) {
2823                    var tid = $(el).parents(".tab-cont").attr("tid");
2824                    var pname = $(el).attr("name").toLowerCase();
2825                    var pval = "";
2826                    if ($(el).is("input,textrea,select")) {
2827                        pval = $(el).val();
2828                    } else {
2829                        pval = $(el).html();
2830                    }
2831                    var validation = opt.tabs[tid]["input"][i]["validation"];
2832                    if (typeof(validation) != "undefined") {
2833                        if (!pval.match(new RegExp(validation, "i"))) {
2834                            valid = false;
2835                            $(el).after('<span class="wbbm-inperr">' + CURLANG.validation_err + '</span>').addClass("wbbm-brdred");
2836                        }
2837                    }
2838                    params[pname] = pval;
2839                }, this));
2840                if (valid) {
2841                    $.log("Last range: " + this.lastRange);
2842                    this.selectLastRange();
2843                    //insert callback
2844                    if (queryState) {
2845                        this.wbbRemoveCallback(cmd, true);
2846                    }
2847                    this.wbbInsertCallback(cmd, params);
2848                    //END insert callback
2849
2850                    this.closeModal();
2851                    this.updateUI();
2852                }
2853            }, this));
2854            $wbbm.find('#wbbm-remove').click($.proxy(function () {
2855                //clbk.remove();
2856                this.selectLastRange();
2857                this.wbbRemoveCallback(cmd); //remove callback
2858                this.closeModal();
2859                this.updateUI();
2860            }, this));
2861
2862            $(document.body).css("overflow", "hidden"); //lock the screen, remove scroll on body
2863            if ($("body").height() > $(window).height()) { //if body has scroll, add padding-right 18px
2864                $(document.body).css("padding-right", "18px");
2865            }
2866            this.$modal.show();
2867            //if (window.getSelection) 
2868            if (this.isMobile) {
2869                $wbbm.css("margin-top", "10px");
2870            } else {
2871                $wbbm.css("margin-top", ($(window).height() - $wbbm.outerHeight()) / 3 + "px");
2872            }
2873            //setTimeout($.proxy(function() {this.$modal.find("input:visible")[0].focus()},this),10);
2874            setTimeout($.proxy(function () {
2875                this.$modal.find(".inp-text:visible")[0].focus()
2876            }, this), 10);
2877        },
2878        escModal: function (e) {
2879            if (e.which == 27) {
2880                this.closeModal();
2881            }
2882        },
2883        closeModal: function () {
2884            $(document.body).css("overflow", "auto").css("padding-right", "0").unbind("keyup", this.escModal); //ESC key close modal;
2885            this.$modal.find('#wbbm-submit,#wbbm-remove').unbind('click');
2886            this.$modal.hide();
2887            this.lastRange = false;
2888            return this;
2889        },
2890        getParams: function (src, s, offset) {
2891            var params = {};
2892            if (this.options.bbmode) {
2893                //bbmode
2894                var stext = s.match(/\{[\s\S]+?\}/g);
2895                s = this.prepareRGX(s);
2896                var rgx = new RegExp(s, "g");
2897                var val = this.txtArea.value;
2898                if (offset > 0) {
2899                    val = val.substr(offset, val.length - offset);
2900                }
2901                var a = rgx.exec(val);
2902                if (a) {
2903                    $.each(stext, function (i, n) {
2904                        params[n.replace(/\{|\}/g, "").replace(/"/g, "'").toLowerCase()] = a[i + 1];
2905                    });
2906                }
2907            } else {
2908                var rules = this.options.rules[s][0][1];
2909                $.each(rules, $.proxy(function (k, v) {
2910                    var value = "";
2911                    var $v = (v.sel !== false) ? value = $(src).find(v.sel) : $(src);
2912                    if (v.attr !== false) {
2913                        value = $v.attr(v.attr);
2914                    } else {
2915                        value = $v.html();
2916                    }
2917                    if (value) {
2918                        if (v.rgx !== false) {
2919                            var m = value.match(new RegExp(v.rgx));
2920                            if (m && m.length == 2) {
2921                                value = m[1];
2922                            }
2923                        }
2924                        params[k] = value.replace(/"/g, "'");
2925                    }
2926                }, this))
2927            }
2928            return params;
2929        },
2930
2931
2932        //imgUploader
2933        imgLoadModal: function () {
2934            $.log("imgLoadModal");
2935            if (this.options.imgupload === true) {
2936                this.$modal.find("#imguploader").dragfileupload({
2937                    url: this.strf(this.options.img_uploadurl, this.options),
2938                    extraParams: {
2939                        maxwidth: this.options.img_maxwidth,
2940                        maxheight: this.options.img_maxheight
2941                    },
2942                    themePrefix: this.options.themePrefix,
2943                    themeName: this.options.themeName,
2944                    success: $.proxy(function (data) {
2945                        this.$txtArea.insertImage(data.image_link, data.thumb_link);
2946
2947                        this.closeModal();
2948                        this.updateUI();
2949                    }, this)
2950                });
2951
2952                this.$modal.find("#fileupl").bind("change", function () {
2953                    $("#fupform").submit();
2954                });
2955                this.$modal.find("#fupform").bind("submit", $.proxy(function (e) {
2956                    $(e.target).parents("#imguploader").hide().after('<div class="loader"><img src="' + this.options.themePrefix + '/' + this.options.themeName + '/img/loader.gif" /><br/><span>' + CURLANG.loading + '</span></div>').parent().css("text-align", "center");
2957                }, this))
2958
2959            } else {
2960                this.$modal.find(".hastabs").removeClass("hastabs");
2961                this.$modal.find("#imguploader").parents(".tab-cont").remove();
2962                this.$modal.find(".wbbm-tablist").remove();
2963            }
2964        },
2965        imgSubmitModal: function () {
2966            $.log("imgSubmitModal");
2967        },
2968        //DEBUG
2969        printObjectInIE: function (obj) {
2970            try {
2971                $.log(JSON.stringify(obj));
2972            } catch (e) {
2973            }
2974        },
2975        checkFilter: function (node, filter) {
2976            $.log("node: " + $(node).get(0).outerHTML + " filter: " + filter + " res: " + $(node).is(filter.toLowerCase()));
2977        },
2978        debug: function (msg) {
2979            if (this.options.debug === true) {
2980                var time = (new Date()).getTime();
2981                if (typeof(console) != "undefined") {
2982                    console.log((time - this.startTime) + " ms: " + msg);
2983                } else {
2984                    $("#exlog").append('<p>' + (time - this.startTime) + " ms: " + msg + '</p>');
2985                }
2986                this.startTime = time;
2987            }
2988        },
2989
2990        //Browser fixes
2991        isChrome: function () {
2992            return (window.chrome) ? true : false;
2993        },
2994        fixTableTransform: function (html) {
2995            if (!html) {
2996                return "";
2997            }
2998            if ($.inArray("table", this.options.buttons) == -1) {
2999                return html.replace(/\<(\/*?(table|tr|td|tbody))[^>]*\>/ig, "");
3000            } else {
3001                return html.replace(/\<(\/*?(table|tr|td))[^>]*\>/ig, "[$1]".toLowerCase()).replace(/\<\/*tbody[^>]*\>/ig, "");
3002            }
3003        }
3004    };
3005
3006    $.log = function (msg) {
3007        if (typeof(wbbdebug) != "undefined" && wbbdebug === true) {
3008            if (typeof(console) != "undefined") {
3009                console.log(msg);
3010            } else {
3011                $("#exlog").append('<p>' + msg + '</p>');
3012            }
3013        }
3014    };
3015    $.fn.wysibb = function (settings) {
3016        return this.each(function () {
3017            var data = $(this).data("wbb");
3018            if (!data) {
3019                new $.wysibb(this, settings);
3020            }
3021        });
3022    };
3023    $.fn.wdrag = function (opt) {
3024        if (!opt.scope) {
3025            opt.scope = this;
3026        }
3027        var start = {x: 0, y: 0, height: 0};
3028        var drag;
3029        opt.scope.drag_mousedown = function (e) {
3030            e.preventDefault();
3031            start = {
3032                x: e.pageX,
3033                y: e.pageY,
3034                height: opt.height,
3035                sheight: opt.scope.$body.height()
3036            };
3037            drag = true;
3038            $(document).bind("mousemove", $.proxy(opt.scope.drag_mousemove, this));
3039            $(this).addClass("drag");
3040        };
3041        opt.scope.drag_mouseup = function (e) {
3042            if (drag === true) {
3043                e.preventDefault();
3044                $(document).unbind("mousemove", opt.scope.drag_mousemove);
3045                $(this).removeClass("drag");
3046                drag = false;
3047            }
3048        };
3049        opt.scope.drag_mousemove = function (e) {
3050            e.preventDefault();
3051            var axisX = 0, axisY = 0;
3052            if (opt.axisX) {
3053                axisX = e.pageX - start.x;
3054            }
3055            if (opt.axisY) {
3056                axisY = e.pageY - start.y;
3057            }
3058            if (axisY != 0) {
3059                var nheight = start.sheight + axisY;
3060                if (nheight > start.height && nheight <= opt.scope.options.resize_maxheight) {
3061                    if (opt.scope.options.bbmode == true) {
3062                        opt.scope.$txtArea.css((opt.scope.options.autoresize === true) ? "min-height" : "height", nheight + "px");
3063                    } else {
3064                        opt.scope.$body.css((opt.scope.options.autoresize === true) ? "min-height" : "height", nheight + "px");
3065                    }
3066                }
3067            }
3068        };
3069
3070
3071        $(this).bind("mousedown", opt.scope.drag_mousedown);
3072        $(document).bind("mouseup", $.proxy(opt.scope.drag_mouseup, this));
3073    },
3074
3075        //API
3076        $.fn.getDoc = function () {
3077            return this.data('wbb').doc;
3078        };
3079    $.fn.getSelectText = function (fromTextArea) {
3080        return this.data('wbb').getSelectText(fromTextArea);
3081    };
3082    $.fn.bbcode = function (data) {
3083        if (typeof(data) != "undefined") {
3084            if (this.data('wbb').options.bbmode) {
3085                this.data('wbb').$txtArea.val(data);
3086            } else {
3087                this.data('wbb').$body.html(this.data("wbb").getHTML(data));
3088            }
3089            return this;
3090        } else {
3091            return this.data('wbb').getBBCode();
3092        }
3093    };
3094    $.fn.htmlcode = function (data) {
3095        if (!this.data('wbb').options.onlyBBMode && this.data('wbb').inited === true) {
3096            if (typeof(data) != "undefined") {
3097                this.data('wbb').$body.html(data);
3098                return this;
3099            } else {
3100                return this.data('wbb').getHTML(this.data('wbb').$txtArea.val());
3101            }
3102        }
3103    };
3104    $.fn.getBBCode = function () {
3105        return this.data('wbb').getBBCode();
3106    };
3107    $.fn.getHTML = function () {
3108        var wbb = this.data('wbb');
3109        return wbb.getHTML(wbb.$txtArea.val());
3110    };
3111    $.fn.getHTMLByCommand = function (command, params) {
3112        return this.data("wbb").getHTMLByCommand(command, params);
3113    };
3114    $.fn.getBBCodeByCommand = function (command, params) {
3115        return this.data("wbb").getBBCodeByCommand(command, params);
3116    };
3117    $.fn.insertAtCursor = function (data, forceBBMode) {
3118        this.data("wbb").insertAtCursor(data, forceBBMode);
3119        return this.data("wbb");
3120    };
3121    $.fn.execCommand = function (command, value) {
3122        this.data("wbb").execCommand(command, value);
3123        return this.data("wbb");
3124    };
3125    $.fn.insertImage = function (imgurl, thumburl) {
3126        var editor = this.data("wbb");
3127        var code = (thumburl) ? editor.getCodeByCommand('link', {url: imgurl, seltext: editor.getCodeByCommand('img', {src: thumburl})}) : editor.getCodeByCommand('img', {src: imgurl});
3128        this.insertAtCursor(code);
3129        return editor;
3130    };
3131    $.fn.sync = function () {
3132        this.data("wbb").sync();
3133        return this.data("wbb");
3134    };
3135    $.fn.destroy = function () {
3136        this.data("wbb").destroy();
3137    };
3138
3139
3140    $.fn.queryState = function (command) {
3141        return this.data("wbb").queryState(command);
3142    }
3143})(jQuery);
3144
3145
3146//Drag&Drop file uploader
3147(function ($) {
3148    'use strict';
3149
3150    $.fn.dragfileupload = function (options) {
3151        return this.each(function () {
3152            var upl = new FileUpload(this, options);
3153            upl.init();
3154        });
3155    };
3156
3157    function FileUpload(e, options) {
3158        this.$block = $(e);
3159
3160        this.opt = $.extend({
3161            url: false,
3162            success: false,
3163            extraParams: false,
3164            fileParam: 'img',
3165            validation: '\.(jpg|png|gif|jpeg)$',
3166
3167            t1: CURLANG.fileupload_text1,
3168            t2: CURLANG.fileupload_text2
3169        }, options);
3170    }
3171
3172    FileUpload.prototype = {
3173        init: function () {
3174            if (window.FormData != null) {
3175                this.$block.addClass("drag");
3176                this.$block.prepend('<div class="p2">' + this.opt.t2 + '</div>');
3177                this.$block.prepend('<div class="p">' + this.opt.t1 + '</div>');
3178
3179                this.$block.bind('dragover', function () {
3180                    $(this).addClass('dragover');
3181                    return false;
3182                });
3183                this.$block.bind('dragleave', function () {
3184                    $(this).removeClass('dragover');
3185                    return false;
3186                });
3187
3188                //upload progress
3189                var uploadProgress = $.proxy(function (e) {
3190                    var p = parseInt(e.loaded / e.total * 100, 10);
3191                    this.$loader.children("span").text(CURLANG.loading + ': ' + p + '%');
3192
3193                }, this);
3194                var xhr = jQuery.ajaxSettings.xhr();
3195                if (xhr.upload) {
3196                    xhr.upload.addEventListener('progress', uploadProgress, false);
3197                }
3198                this.$block[0].ondrop = $.proxy(function (e) {
3199                    e.preventDefault();
3200                    this.$block.removeClass('dragover');
3201                    var ufile = e.dataTransfer.files[0];
3202                    if (this.opt.validation && !ufile.name.match(new RegExp(this.opt.validation))) {
3203                        this.error(CURLANG.validation_err);
3204                        return false;
3205                    }
3206                    var fData = new FormData();
3207                    fData.append(this.opt.fileParam, ufile);
3208
3209                    if (this.opt.extraParams) { //check for extraParams to upload
3210                        $.each(this.opt.extraParams, function (k, v) {
3211                            fData.append(k, v);
3212                        });
3213                    }
3214
3215                    this.$loader = $('<div class="loader"><img src="' + this.opt.themePrefix + '/' + this.opt.themeName + '/img/loader.gif" /><br/><span>' + CURLANG.loading + '</span></div>');
3216                    this.$block.html(this.$loader);
3217
3218                    $.ajax({
3219                        type: 'POST',
3220                        url: this.opt.url,
3221                        data: fData,
3222                        processData: false,
3223                        contentType: false,
3224                        xhr: function () {
3225                            return xhr
3226                        },
3227                        dataType: 'json',
3228                        success: $.proxy(function (data) {
3229                            if (data && data.status == 1) {
3230                                this.opt.success(data);
3231                            } else {
3232                                this.error(data.msg || CURLANG.error_onupload);
3233                            }
3234                        }, this),
3235                        error: $.proxy(function (xhr, txt, thr) {
3236                            this.error(CURLANG.error_onupload)
3237                        }, this)
3238                    });
3239                }, this);
3240
3241            }
3242        },
3243        error: function (msg) {
3244            this.$block.find(".upl-error").remove().end().append('<span class="upl-error">' + msg + '</span>').addClass("wbbm-brdred");
3245        }
3246    }
3247})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.