PageSourceSearch

https://omsk.com/js/jquery.wysibb.js

js omsk.com collected 2026-10-02 17:13:32 UTC 100,428 bytes, 3,026 lines download raw bytes

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