PageSourceSearch

https://photo-monster.ru/forum/extensions/monster_editor/assets/js/jquery.wysibb.js?1375969394

js photo-monster.ru collected 2026-10-02 11:06:13 UTC 99,486 bytes, 2,915 lines download raw bytes

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