PageSourceSearch

https://voenchast.ru/bitrix/js/fileman/html_editor/html-base-controls.js

js voenchast.ru collected 2026-10-09 22:40:41 UTC 100,707 bytes, 4,090 lines download raw bytes

1/**
2 * Bitrix HTML Editor 3.0
3 * Date: 24.04.13
4 * Time: 4:23
5 *
6 * Taskbarmanager
7 * Taskbar
8 * Context Menu
9 * Search/Replace
10 */
11;(function() {
12	function TaskbarManager(editor, init)
13	{
14		this.editor = editor;
15		this.bShown = false;
16		this.closedWidth = 20;
17		this.MIN_CLOSED_WIDTH = 120;
18		this.width = this.editor.config.taskbarWidth || 250;
19		this.taskbars = {};
20		this.freezeOnclickHandler = false;
21
22		if (init)
23		{
24			this.Init();
25		}
26	}
27
28	TaskbarManager.prototype = {
29		Init: function()
30		{
31			this.pCont = this.editor.dom.taskbarCont;
32			this.pCont.setAttribute('data-bx-type', 'taskbarmanager');
33
34			this.pResizer = BX('bx-html-editor-tskbr-res-' + this.editor.id);
35			this.pResizer.setAttribute('data-bx-type', 'taskbarflip');
36			this.pTopCont = BX('bx-html-editor-tskbr-top-' + this.editor.id);
37
38			BX.bind(this.pResizer, 'mousedown', BX.proxy(this.StartResize, this));
39			BX.bind(this.pCont, 'click', BX.proxy(this.OnClick, this));
40
41			// Search
42			this.pSearchCont = BX('bxhed-tskbr-search-cnt-' + this.editor.id);
43			this.pSearchAli = BX('bxhed-tskbr-search-ali-' + this.editor.id);
44			this.pSearchInput = BX('bxhed-tskbr-search-inp-' + this.editor.id);
45			this.pSearchNothingNotice = BX('bxhed-tskbr-search-nothing-' + this.editor.id);
46			BX.bind(this.pSearchInput, 'keyup', BX.proxy(this.TaskbarSearch, this));
47		},
48
49		OnClick: function(e)
50		{
51			if (!e)
52				e = window.event;
53
54			if (this.freezeOnclickHandler)
55				return;
56
57			var
58				_this = this,
59				target = e.target || e.srcElement,
60				type = (target && target.getAttribute) ? target.getAttribute('data-bx-type') : null;
61
62			if (!type)
63			{
64				target = BX.findParent(target, function(node)
65				{
66					return node == _this.pCont || (node.getAttribute && node.getAttribute('data-bx-type'));
67				}, this.pCont);
68				type = (target && target.getAttribute) ? target.getAttribute('data-bx-type') : null;
69			}
70
71			if (type == 'taskbarflip' || (!this.bShown && (type == 'taskbarmanager' || !type)))
72			{
73				if (this.bShown)
74				{
75					this.Hide();
76				}
77				else
78				{
79					this.Show();
80				}
81			}
82			else if(type == 'taskbargroup_title')
83			{
84				BX.onCustomEvent(this, 'taskbargroupTitleClick', [target]);
85			}
86			else if(type == 'taskbarelement')
87			{
88				BX.onCustomEvent(this, 'taskbarelementClick', [target]);
89			}
90			else if(type == 'taskbar_title_but')
91			{
92				BX.onCustomEvent(this, 'taskbarTitleClick', [target]);
93			}
94			else if(type == 'taskbar_top_menu')
95			{
96				BX.onCustomEvent(this, 'taskbarMenuClick', [target]);
97			}
98			else if(type == 'taskbar_search_cancel')
99			{
100				this.pSearchInput.value = '';
101				this.TaskbarSearch();
102			}
103		},
104
105		Show: function(saveValue)
106		{
107			if (!this.bShown)
108			{
109				this.bShown = true;
110				this.pCont.className = 'bxhtmled-taskbar-cnt bxhtmled-taskbar-shown';
111			}
112			this.pCont.style.width = this.GetWidth(true) + 'px';
113			this.editor.ResizeSceleton();
114
115			if (saveValue !== false)
116			{
117				this.editor.SaveOption('taskbar_shown', 1);
118			}
119		},
120
121		Hide: function(saveValue)
122		{
123			if (this.bShown)
124			{
125				this.bShown = false;
126				this.pCont.className = 'bxhtmled-taskbar-cnt bxhtmled-taskbar-hidden';
127			}
128			this.pCont.style.width = this.GetWidth() + 'px';
129			this.editor.ResizeSceleton();
130
131			if (saveValue !== false)
132			{
133				this.editor.SaveOption('taskbar_shown', 0);
134			}
135		},
136
137		GetWidth: function(bCheck, maxWidth)
138		{
139			var width;
140			if (this.bShown)
141			{
142				width = bCheck ? Math.max(this.width, this.closedWidth + this.MIN_CLOSED_WIDTH) : this.width;
143				if(maxWidth && width > maxWidth)
144				{
145					width = this.width = Math.round(maxWidth);
146				}
147			}
148			else
149			{
150				width = this.closedWidth;
151			}
152
153			return width;
154		},
155
156		AddTaskbar: function(oTaskbar)
157		{
158			this.taskbars[oTaskbar.id] = oTaskbar;
159			this.pCont.appendChild(oTaskbar.GetCont());
160			this.pTopCont.appendChild(oTaskbar.GetTitleCont());
161		},
162
163		ShowTaskbar: function(taskbarId)
164		{
165			this.pSearchInput.value = '';
166			for(var id in this.taskbars)
167			{
168				if (this.taskbars.hasOwnProperty(id))
169				{
170					if (id == taskbarId)
171					{
172						this.taskbars[id].Activate();
173						this.pSearchInput.placeholder = this.taskbars[id].searchPlaceholder;
174					}
175					else
176					{
177						this.taskbars[id].Deactivate();
178					}
179
180					this.activeTaskbarId = taskbarId;
181					this.taskbars[id].ClearSearchResult();
182				}
183			}
184		},
185
186		GetActiveTaskbar: function()
187		{
188			return this.taskbars[this.activeTaskbarId];
189		},
190
191		StartResize: function(e)
192		{
193			if(!e)
194				e = window.event;
195
196			var target = e.target || e.srcElement;
197			if (target.getAttribute('data-bx-tsk-split-but') == 'Y')
198				return true;
199
200			this.freezeOnclickHandler = true;
201
202			var
203				width = this.GetWidth(),
204				overlay = this.editor.dom.resizerOverlay,
205				dX = 0, newWidth,
206				windowScroll = BX.GetWindowScrollPos(),
207				startX = e.clientX + windowScroll.scrollLeft,
208				_this = this;
209
210			overlay.style.display = 'block';
211
212			function moveResizer(e, bFinish)
213			{
214				if(!e)
215					e = window.event;
216
217				var x = e.clientX + windowScroll.scrollLeft;
218
219				if(startX == x)
220					return;
221
222				dX = startX - x;
223				newWidth = width + dX;
224
225				if (bFinish)
226				{
227					_this.width = Math.max(newWidth, _this.closedWidth + _this.MIN_CLOSED_WIDTH);
228					if (isNaN(_this.width))
229					{
230						_this.width = _this.closedWidth + _this.MIN_CLOSED_WIDTH;
231					}
232				}
233				else
234				{
235					_this.width = newWidth;
236				}
237
238				if (newWidth > _this.closedWidth + (bFinish ? 20 : 0))
239				{
240					_this.Show();
241				}
242				else
243				{
244					_this.Hide();
245				}
246			}
247
248			function finishResizing(e)
249			{
250				moveResizer(e, true);
251				BX.unbind(document, 'mousemove', moveResizer);
252				BX.unbind(document, 'mouseup', finishResizing);
253				overlay.style.display = 'none';
254				setTimeout(function(){_this.freezeOnclickHandler = false;}, 10);
255				BX.PreventDefault(e);
256
257				_this.editor.SaveOption('taskbar_width', _this.GetWidth(true));
258			}
259
260			BX.bind(document, 'mousemove', moveResizer);
261			BX.bind(document, 'mouseup', finishResizing);
262		},
263
264		Resize: function(w, h)
265		{
266			var topHeight = parseInt(this.pTopCont.offsetHeight, 10);
267			for(var id in this.taskbars)
268			{
269				if (this.taskbars.hasOwnProperty(id) && this.taskbars[id].pTreeCont)
270				{
271					this.taskbars[id].pTreeCont.style.height = (h - topHeight - 42) + 'px';
272				}
273			}
274
275			this.pSearchCont.style.width = w + 'px';
276			if (!BX.browser.IsDoctype())
277			{
278				this.pSearchAli.style.width = (w - 20) + 'px';
279			}
280
281			var _this = this;
282			if (this.resizeTimeout)
283			{
284				this.resizeTimeout = clearTimeout(this.resizeTimeout);
285			}
286
287			this.resizeTimeout = setTimeout(function()
288			{
289				if (parseInt(_this.pTopCont.offsetHeight, 10) !== topHeight)
290				{
291					_this.Resize(w, h);
292				}
293			}, 100);
294		},
295
296		TaskbarSearch: function(e)
297		{
298			var
299				taskbar = this.GetActiveTaskbar(),
300				value = this.pSearchInput.value;
301
302			if (e && e.keyCode == this.editor.KEY_CODES['escape'])
303			{
304				value = this.pSearchInput.value = '';
305			}
306
307			if (value.length < 2)
308			{
309				taskbar.ClearSearchResult();
310			}
311			else
312			{
313				taskbar.Search(value);
314			}
315		}
316	};
317
318
319	/*
320	 *
321	 *
322	 * */
323	function Taskbar(editor)
324	{
325		this.editor = editor;
326		this.manager = this.editor.taskbarManager;
327		this.searchIndex = [];
328		this._searchResult = [];
329		this._searchResultSect = [];
330
331		BX.addCustomEvent(this.manager, 'taskbargroupTitleClick', BX.proxy(this.OnGroupTitleClick, this));
332		BX.addCustomEvent(this.manager, 'taskbarelementClick', BX.proxy(this.OnElementClick, this));
333		BX.addCustomEvent(this.manager, 'taskbarTitleClick', BX.proxy(this.OnTitleClick, this));
334		BX.addCustomEvent(this.manager, 'taskbarMenuClick', BX.proxy(this.OnMenuClick, this));
335	}
336
337	Taskbar.prototype = {
338		GetCont: function()
339		{
340			return this.pTreeCont;
341		},
342
343		GetTitleCont: function()
344		{
345			return this.pTitleCont;
346		},
347
348		BuildSceleton: function()
349		{
350			// Build title & menu
351			this.pTitleCont = BX.create("span", {props: {className: "bxhtmled-split-btn"},html: '<span class="bxhtmled-split-btn-l"><span class="bxhtmled-split-btn-bg">' + this.title + '</span></span><span class="bxhtmled-split-btn-r"><span data-bx-type="taskbar_top_menu" data-bx-taskbar="' + this.id + '" class="bxhtmled-split-btn-bg"></span></span>'});
352			this.pTitleCont.setAttribute('data-bx-type', 'taskbar_title_but');
353			this.pTitleCont.setAttribute('data-bx-taskbar', this.id);
354
355			this.pTreeCont = BX.create("DIV", {props: {className: "bxhtmled-taskbar-tree-cont"}});
356			this.pTreeInnerCont = this.pTreeCont.appendChild(BX.create("DIV", {props: {className: "bxhtmled-taskbar-tree-inner-cont"}}));
357		},
358
359		BuildTree: function(sections, elements)
360		{
361			BX.cleanNode(this.pTreeCont);
362			this.treeSectionIndex = {};
363			this.BuildTreeSections(sections);
364			this.BuildTreeElements(elements);
365		},
366
367		BuildTreeSections: function(sections)
368		{
369			this.sections = [];
370			for (var i = 0; i < sections.length; i++)
371			{
372				this.BuildSection(sections[i]);
373			}
374		},
375
376		GetSectionsTreeInfo: function()
377		{
378			return this.sections;
379		},
380
381		BuildSection: function(section)
382		{
383			var
384				parentCont = this.GetSectionContByPath(section.path),
385				pGroup = BX.create("DIV", {props: {className: "bxhtmled-tskbr-sect-outer"}}),
386				pGroupTitle = pGroup.appendChild(BX.create("DIV", {props: {className: "bxhtmled-tskbr-sect"}})),
387				icon = pGroupTitle.appendChild(BX.create("SPAN", {props: {className: "bxhtmled-tskbr-sect-icon"}})),
388				title = pGroupTitle.appendChild(BX.create("SPAN", {props: {className: "bxhtmled-tskbr-sect-title"}, text: section.title || section.name})),
389				childCont = pGroup.appendChild(BX.create("DIV", {props: {className: "bxhtmled-tskb-child"}})),
390				elementsCont = pGroup.appendChild(BX.create("DIV", {props: {className: "bxhtmled-tskb-child-elements"}}));
391
392			var key = section.path == '' ? section.name : section.path + ',' + section.name;
393			var depth = section.path == '' ? 0 : 1; // Todo....
394
395			var sect = {
396				key: key,
397				children: [],
398				section: section
399			}
400
401			this.treeSectionIndex[key] = {
402				icon: icon,
403				outerCont: pGroup,
404				cont: pGroupTitle,
405				childCont: childCont,
406				elementsCont: elementsCont,
407				sect: sect
408			};
409
410			this.GetSectionByPath(section.path).push(sect);
411
412			if (depth > 0)
413			{
414				BX.addClass(pGroupTitle, "bxhtmled-tskbr-sect-" + depth);
415				BX.addClass(icon, "bxhtmled-tskbr-sect-icon-" + depth);
416			}
417
418			pGroupTitle.setAttribute('data-bx-type', 'taskbargroup_title');
419			pGroupTitle.setAttribute('data-bx-taskbar', this.id);
420
421			pGroup.setAttribute('data-bx-type', 'taskbargroup');
422			pGroup.setAttribute('data-bx-path', key);
423			pGroup.setAttribute('data-bx-taskbar', this.id);
424
425			parentCont.appendChild(pGroup);
426		},
427
428		BuildTreeElements: function(elements)
429		{
430			this.elements = elements;
431			for (var i in elements)
432			{
433				if (elements.hasOwnProperty(i))
434				{
435					this.BuildElement(elements[i]);
436				}
437			}
438		},
439
440		BuildElement: function(element)
441		{
442			const _this = this;
443			const parentCont = this.GetSectionContByPath(element.key || element.path, true);
444			const pElement = BX.create('DIV', {
445				props: { className: 'bxhtmled-tskbr-element' },
446				html: '<span class="bxhtmled-tskbr-element-icon"></span><span class="bxhtmled-tskbr-element-text">'
447					+ BX.Text.encode(element.title)
448				+ '</span>',
449			});
450
451			var dd = pElement.appendChild(BX.create("IMG", {props: {
452				src: this.editor.util.GetEmptyImage(),
453				className: "bxhtmled-drag"
454			}}));
455
456			this.HandleElementEx(pElement, dd, element);
457
458			this.searchIndex.push({
459				content: (BX.Text.encode(element.title) + ' ' + element.name).toLowerCase(),
460				element: pElement
461			});
462
463			dd.onmousedown = function (e)
464			{
465				if (!e)
466				{
467					e = window.event;
468				}
469
470				var
471					target = e.target || e.srcElement,
472					bxTag = _this.editor.GetBxTag(target);
473
474				return _this.OnElementMouseDownEx(e, target, bxTag);
475			};
476
477			dd.ondblclick = function(e)
478			{
479				var
480					target = e.target || e.srcElement,
481					bxTag = _this.editor.GetBxTag(target);
482
483				return _this.OnElementDoubleClick(e, target, bxTag);
484			};
485
486			dd.ondragend = function (e)
487			{
488				if (!e)
489				{
490					e = window.event;
491				}
492				_this.OnDragEndHandler(e, this);
493			};
494
495			pElement.setAttribute('data-bx-type', 'taskbarelement');
496
497			parentCont.appendChild(pElement);
498		},
499
500		HandleElementEx: function(dd)
501		{
502
503		},
504
505		GetSectionContByPath: function(path, bElement)
506		{
507			if (path == '' || !this.treeSectionIndex[path])
508			{
509				return this.pTreeCont;
510			}
511			else
512			{
513				return bElement ? this.treeSectionIndex[path].elementsCont : this.treeSectionIndex[path].childCont;
514			}
515		},
516
517		GetSectionByPath: function(path)
518		{
519			if (path == '' || !this.treeSectionIndex[path])
520			{
521				return this.sections;
522			}
523			else
524			{
525				return this.treeSectionIndex[path].sect.children;
526			}
527		},
528
529		// Open or close
530		ToggleGroup: function(cont, bOpen)
531		{
532			// TODO: animation
533			var path = cont.getAttribute('data-bx-path');
534			if (path)
535			{
536				var group = this.treeSectionIndex[path];
537				if (!group)
538				{
539					return;
540				}
541
542				if (bOpen !== undefined)
543				{
544					group.opened = !bOpen;
545				}
546
547				if (group.opened)
548				{
549					BX.removeClass(group.cont, 'bxhtmled-tskbr-sect-open');
550					BX.removeClass(group.icon, 'bxhtmled-tskbr-sect-icon-open');
551					BX.removeClass(group.outerCont, 'bxhtmled-tskbr-sect-outer-open');
552					group.childCont.style.display = 'none';
553					group.elementsCont.style.display = 'none';
554					group.opened = false;
555				}
556				else
557				{
558					BX.addClass(group.cont, 'bxhtmled-tskbr-sect-open');
559					BX.addClass(group.icon, 'bxhtmled-tskbr-sect-icon-open');
560					BX.addClass(group.outerCont, 'bxhtmled-tskbr-sect-outer-open');
561					group.childCont.style.display = 'block';
562					group.elementsCont.style.display = group.elementsCont.childNodes.length > 0 ? 'block' : 'none';
563					group.opened = true;
564				}
565			}
566		},
567
568		OnDragEndHandler: function(e, node)
569		{
570			var _this = this;
571			this.editor.skipPasteHandler = true;
572			this.editor.skipPasteControl = true;
573
574			if (this.editor.iframeView.pasteHandlerTimeout)
575				this.editor.iframeView.pasteHandlerTimeout = clearTimeout(this.editor.iframeView.pasteHandlerTimeout);
576
577			setTimeout(function()
578			{
579				var dd = _this.editor.GetIframeElement(node.id);
580				if (dd && dd.parentNode)
581				{
582					var sur = _this.editor.util.CheckSurrogateNode(dd.parentNode);
583					if (sur)
584					{
585						_this.editor.util.InsertAfter(dd, sur);
586					}
587				}
588				_this.editor.synchro.FullSyncFromIframe();
589
590				_this.editor.skipPasteHandler = false;
591				_this.editor.skipPasteControl = false;
592				if (_this.editor.iframeView.pasteLoader && _this.editor.iframeView.pasteLoader.hide)
593				{
594					_this.editor.iframeView.pasteLoader.hide();
595				}
596			}, 20);
597		},
598
599		OnElementMouseDownEx: function(e)
600		{
601			return true;
602		},
603
604		OnElementClick: function(e)
605		{
606			this.OnElementClickEx();
607			return true;
608		},
609
610		OnElementClickEx: function()
611		{
612			return true;
613		},
614
615		OnElementDoubleClick: function(e, target, bxTag)
616		{
617			if (target)
618			{
619				var dd = target.cloneNode(true);
620				this.editor.Focus();
621				this.editor.selection.InsertNode(dd);
622				this.editor.synchro.FullSyncFromIframe();
623			}
624		},
625
626		OnGroupTitleClick: function(pElement)
627		{
628			if (pElement && pElement.getAttribute('data-bx-taskbar') == this.id)
629			{
630				return this.ToggleGroup(pElement.parentNode);
631			}
632			return true;
633		},
634
635		OnTitleClick: function(pElement)
636		{
637			if (pElement && pElement.getAttribute('data-bx-taskbar') == this.id)
638			{
639				return this.manager.ShowTaskbar(this.id);
640			}
641			return true;
642		},
643
644		OnMenuClick: function(pElement)
645		{
646			if (pElement && pElement.getAttribute('data-bx-taskbar') == this.id)
647				return this.ShowMenu(pElement);
648			return true;
649		},
650
651		Activate: function()
652		{
653			this.pTreeCont.style.display = 'block';
654			this.bActive = true;
655			return true;
656		},
657
658		Deactivate: function()
659		{
660			this.pTreeCont.style.display = 'none';
661			this.bActive = false;
662			return true;
663		},
664
665		IsActive: function()
666		{
667			return !!this.bActive;
668		},
669
670		ShowMenu: function(pElement)
671		{
672			var arItems = this.GetMenuItems();
673			BX.PopupMenu.destroy(this.uniqueId + "_menu");
674			BX.PopupMenu.show(this.uniqueId + "_menu", pElement, arItems, {
675					overlay: {opacity: 0.1},
676					events: {
677						onPopupClose: function(){BX.removeClass(this.bindElement, "bxec-add-more-over");}
678					},
679					offsetLeft: 1,
680					zIndex: 3005
681				}
682			);
683			return true;
684		},
685
686		GetMenuItems: function()
687		{
688			return [];
689		},
690
691		Search: function(value)
692		{
693			this.ClearSearchResult();
694			var
695				bFoundItems = false,
696				pSect, el,
697				i, l = this.searchIndex.length;
698
699			value = BX.util.trim(value.toLowerCase());
700
701			BX.addClass(this.pTreeCont, 'bxhtmled-taskbar-tree-cont-search');
702			BX.addClass(this.manager.pSearchCont, 'bxhtmled-search-cont-res');
703
704			for(i = 0; i < l; i++)
705			{
706				el = this.searchIndex[i];
707				if (el.content.indexOf(value) !== -1) // Show element
708				{
709					bFoundItems = true;
710					BX.addClass(el.element, 'bxhtmled-tskbr-search-res');
711					this._searchResult.push(el.element);
712
713					pSect = BX.findParent(el.element, function(node)
714					{
715						return node.getAttribute && node.getAttribute('data-bx-type') == 'taskbargroup';
716					}, this.pTreeCont);
717
718					while (pSect)
719					{
720						BX.addClass(pSect, 'bxhtmled-tskbr-search-res');
721						this.ToggleGroup(pSect, true);
722						this._searchResultSect.push(pSect);
723
724						pSect = BX.findParent(pSect, function(node)
725						{
726							return node.getAttribute && node.getAttribute('data-bx-type') == 'taskbargroup';
727						}, this.pTreeCont);
728					}
729				}
730			}
731
732			if (!bFoundItems)
733			{
734				this.manager.pSearchNothingNotice.style.display = 'block';
735			}
736		},
737
738		ClearSearchResult: function()
739		{
740			BX.removeClass(this.pTreeCont, 'bxhtmled-taskbar-tree-cont-search');
741			BX.removeClass(this.manager.pSearchCont, 'bxhtmled-search-cont-res');
742			this.manager.pSearchNothingNotice.style.display = 'none';
743			var i;
744			if (this._searchResult)
745			{
746				for(i = 0; i < this._searchResult.length; i++)
747				{
748					BX.removeClass(this._searchResult[i], 'bxhtmled-tskbr-search-res');
749				}
750				this._searchResult = [];
751			}
752			if (this._searchResultSect)
753			{
754				for(i = 0; i < this._searchResultSect.length; i++)
755				{
756					BX.removeClass(this._searchResultSect[i], 'bxhtmled-tskbr-search-res');
757					this.ToggleGroup(this._searchResultSect[i], false);
758				}
759				this._searchResultSect = [];
760			}
761		},
762
763		GetId: function()
764		{
765			return this.id;
766		}
767	};
768
769	function ComponentsControl(editor)
770	{
771		// Call parrent constructor
772		ComponentsControl.superclass.constructor.apply(this, arguments);
773
774		this.id = 'components';
775		this.title = BX.message('ComponentsTitle');
776		this.templateId = this.editor.templateId;
777		this.uniqueId = 'taskbar_' + this.editor.id + '_' + this.id;
778		this.searchPlaceholder = BX.message('BXEdCompSearchPlaceHolder');
779
780		this.Init();
781	}
782
783	BX.extend(ComponentsControl, Taskbar);
784
785	ComponentsControl.prototype.Init = function()
786	{
787		this.BuildSceleton();
788		// Build structure
789		var list = this.editor.components.GetList();
790		this.BuildTree(list.groups, list.items);
791	};
792
793	ComponentsControl.prototype.HandleElementEx = function(wrap, dd, params)
794	{
795		this.editor.SetBxTag(dd, {tag: "component_icon", params: params});
796		if (params.complex == "Y")
797		{
798			params.className = 'bxhtmled-surrogate-green';
799			BX.addClass(wrap, 'bxhtmled-tskbr-element-green');
800			wrap.title = BX.message('BXEdComplexComp');
801		}
802	};
803
804	ComponentsControl.prototype.OnElementMouseDownEx = function(e, target, bxTag)
805	{
806		if (!bxTag || bxTag.tag !== 'component_icon')
807		{
808			return false;
809		}
810
811		this.editor.components.LoadParamsList({
812			name: bxTag.params.name
813		});
814	};
815
816	ComponentsControl.prototype.GetMenuItems = function()
817	{
818		var _this = this;
819		return [
820			{
821				text : BX.message('RefreshTaskbar'),
822				title : BX.message('RefreshTaskbar'),
823				className : "",
824				onclick: function()
825				{
826					_this.editor.componentsTaskbar.ClearSearchResult();
827					_this.editor.components.ReloadList();
828					BX.PopupMenu.destroy(_this.uniqueId + "_menu");
829				}
830			}
831		];
832	};
833
834	// Editor dialog
835	function Dialog(editor, params)
836	{
837		this.editor = editor;
838		this.id = params.id;
839		this.params = params;
840		this.className = "bxhtmled-dialog" + (params.className ? ' ' + params.className : '');
841		this.zIndex = params.zIndex || 3008;
842		this.firstFocus = false;
843		this.Init();
844	}
845
846	Dialog.prototype = {
847		Init: function()
848		{
849			var
850				_this = this,
851				config = {
852					title : this.params.title || this.params.name || '',
853					width: this.params.width || 600,
854					resizable: false
855				};
856
857			if (this.params.resizable)
858			{
859				config.resizable = true;
860				config.min_width = this.params.min_width || 400;
861				config.min_height = this.params.min_height || 250;
862				config.resize_id = this.params.resize_id || this.params.id + '_res';
863			}
864
865			this.oDialog = new BX.CDialog(config);
866			config.height = this.params.height || false;
867
868			BX.addCustomEvent(this.oDialog, 'onWindowResize', BX.proxy(this.OnResize, this));
869			BX.addCustomEvent(this.oDialog, 'onWindowResizeFinished', BX.proxy(this.OnResizeFinished, this));
870			BX.addClass(this.oDialog.PARTS.CONTENT, this.className);
871
872			// Clear dialog height for auto resizing
873			if (!config.height)
874			{
875				this.oDialog.PARTS.CONTENT_DATA.style.height = null;
876			}
877
878			// Buttons
879			this.oDialog.SetButtons([
880				new BX.CWindowButton(
881					{
882						title: BX.message('DialogSave'),
883						className: 'adm-btn-save',
884						action: function()
885						{
886							BX.onCustomEvent(_this, "OnDialogSave");
887							_this.oDialog.Close();
888						}
889					}),
890				this.oDialog.btnCancel
891			]);
892
893			BX.addCustomEvent(this.oDialog, 'onWindowUnRegister', function()
894			{
895				BX.unbind(window, "keydown", BX.proxy(_this.OnKeyDown, _this));
896				_this.dialogShownTimeout = setTimeout(function(){_this.editor.dialogShown = false;}, 300);
897				_this.RestoreWindowOverflow();
898			});
899		},
900
901		Show: function()
902		{
903			var _this = this;
904			this.editor.dialogShown = true;
905			if (this.dialogShownTimeout)
906			{
907				this.dialogShownTimeout = clearTimeout(this.dialogShownTimeout);
908			}
909			this.oDialog.Show();
910
911			var
912				top = parseInt(this.oDialog.DIV.style.top) - 180,
913				scrollPos = BX.GetWindowScrollPos(document),
914				scrollTop = scrollPos.scrollTop,
915				minTop = scrollTop + 50;
916
917			this.oDialog.DIV.style.top = (top > minTop ? top : minTop) + 'px';
918			BX.bind(window, "keydown", BX.proxy(this.OnKeyDown, this));
919
920			this.savedBodyOverflow = this.savedScrollLeft = this.savedScrollTop = false;
921
922			setTimeout(function()
923				{
924					// Hack for Opera
925					if (BX.browser.IsOpera())
926						_this.oDialog.Move(1, 1);
927
928					_this.oDialog.__resizeOverlay();
929
930					if (_this.firstFocus)
931					{
932						BX.focus(_this.firstFocus);
933						if (_this.selectFirstFocus)
934							_this.firstFocus.select();
935					}
936				},
937				100
938			);
939		},
940
941		BuildTabControl: function(pCont, arTabs)
942		{
943			var
944				i,
945				pTabsWrap = BX.create('DIV', {props: {className: 'bxhtmled-dlg-tabs-wrap'}}),
946				pContWrap = BX.create('DIV', {props: {className: 'bxhtmled-dlg-cont-wrap'}});
947
948			for (i = 0; i < arTabs.length; i++)
949			{
950				arTabs[i].tab = pTabsWrap.appendChild(BX.create('SPAN', {props: {className: 'bxhtmled-dlg-tab' + (i == 0 ? ' bxhtmled-dlg-tab-active' : '')}, attrs: {'data-bx-dlg-tab-ind': i.toString()}, text: arTabs[i].name}));
951				arTabs[i].cont = pContWrap.appendChild(BX.create('DIV', {props: {className: 'bxhtmled-dlg-cont'}, style: {'display' : i == 0 ? '' : 'none'}}));
952			}
953
954			BX.bind(pTabsWrap, 'click', function(e)
955			{
956				var
957					ind,
958					target = e.target || e.srcElement;
959
960				if (target && target.getAttribute)
961				{
962					ind = parseInt(target.getAttribute('data-bx-dlg-tab-ind'));
963					if (!isNaN(ind))
964					{
965						for (i = 0; i < arTabs.length; i++)
966						{
967							if (i == ind)
968							{
969								arTabs[i].cont.style.display = '';
970								BX.addClass(arTabs[i].tab, 'bxhtmled-dlg-tab-active');
971							}
972							else
973							{
974								arTabs[i].cont.style.display = 'none';
975								BX.removeClass(arTabs[i].tab, 'bxhtmled-dlg-tab-active');
976							}
977						}
978					}
979				}
980			});
981
982			pCont.appendChild(pTabsWrap);
983			pCont.appendChild(pContWrap);
984
985			return {
986				cont: pCont,
987				tabsWrap : pTabsWrap,
988				contWrap : pContWrap,
989				tabs: arTabs
990			};
991		},
992
993		OnKeyDown: function(e)
994		{
995			if (e.keyCode == 13 && this.closeByEnter !== false)
996			{
997				var target = e.target || e.srcElement;
998				if (target && target.nodeName !== 'TEXTAREA')
999				{
1000					this.oDialog.PARAMS.buttons[0].emulate();
1001				}
1002			}
1003		},
1004
1005		SetContent: function(html)
1006		{
1007			return this.oDialog.SetContent(html);
1008		},
1009
1010		SetTitle: function(title)
1011		{
1012			return this.oDialog.SetTitle(title);
1013		},
1014
1015		OnResize: function()
1016		{
1017		},
1018
1019		OnResizeFinished: function()
1020		{
1021		},
1022
1023		GetContentSize: function()
1024		{
1025			return {
1026				width : this.oDialog.PARTS.CONTENT_DATA.offsetWidth,
1027				height : this.oDialog.PARTS.CONTENT_DATA.offsetHeight
1028			};
1029		},
1030
1031		Save: function()
1032		{
1033			if (this.savedRange)
1034			{
1035				this.editor.selection.SetBookmark(this.savedRange);
1036			}
1037
1038			if (this.action && this.editor.action.IsSupported(this.action))
1039			{
1040				this.editor.action.Exec(this.action, this.GetValues());
1041			}
1042		},
1043
1044		Close: function()
1045		{
1046			if (this.IsOpen())
1047			{
1048				this.oDialog.Close();
1049			}
1050		},
1051
1052		IsOpen: function()
1053		{
1054			return this.oDialog.isOpen;
1055		},
1056
1057		DisableKeyCheck: function()
1058		{
1059			this.closeByEnter = false;
1060			BX.WindowManager.disableKeyCheck();
1061		},
1062
1063		EnableKeyCheck: function()
1064		{
1065			var _this = this;
1066			setTimeout(function()
1067			{
1068				_this.closeByEnter = true;
1069				BX.WindowManager.enableKeyCheck();
1070			}, 200);
1071		},
1072
1073		AddTableRow: function (tbl, firstCell)
1074		{
1075			var r, c1, c2;
1076
1077			r = tbl.insertRow(-1);
1078			c1 = r.insertCell(-1);
1079			c1.className = 'bxhtmled-left-c';
1080
1081			if (firstCell && firstCell.label)
1082			{
1083				c1.appendChild(BX.create('LABEL', {props: {className: firstCell.required ? 'bxhtmled-req' : ''}, text: firstCell.label})).setAttribute('for', firstCell.id);
1084			}
1085
1086			c2 = r.insertCell(-1);
1087			c2.className = 'bxhtmled-right-c';
1088			return {row: r, leftCell: c1, rightCell: c2};
1089		},
1090
1091		SetValues: BX.DoNothing,
1092		GetValues: BX.DoNothing,
1093
1094		CheckSize: function(timeout)
1095		{
1096			var _this = this;
1097
1098			if (this.checkSizeTimeout)
1099				this.checkSizeTimeout = clearTimeout(this.checkSizeTimeout);
1100
1101			if (timeout !== true)
1102			{
1103				this.checkSizeTimeout = setTimeout(function()
1104				{
1105					_this.CheckSize(true);
1106				}, 50);
1107				return;
1108			}
1109
1110			var
1111				innerSize = BX.GetWindowInnerSize(document),
1112				dialogBottom = this.oDialog.DIV.offsetHeight + 50;
1113
1114			if (dialogBottom >
1114= innerSize.innerHeight)
1115			{
1116				var scrollPos = BX.GetWindowScrollPos(document);
1117				this.savedBodyOverflow = document.body.style.overflow;
1118				this.savedScrollTop = scrollPos.scrollTop;
1119				this.savedScrollLeft = scrollPos.scrollLeft;
1120				document.body.style.overflow = "auto";
1121
1122				if (this.editor.expanded)
1123					BX.unbind(window, "scroll", BX.proxy(this.editor.PreventScroll, this.editor));
1124			}
1125			else
1126			{
1127				this.RestoreWindowOverflow();
1128			}
1129		},
1130
1131		RestoreWindowOverflow: function()
1132		{
1133			if (this.savedBodyOverflow !== false)
1134			{
1135				document.body.style.overflow = this.savedBodyOverflow;
1136				this.savedBodyOverflow = false;
1137			}
1138
1139			if (this.savedScrollTop !== false)
1140			{
1141				window.scrollTo(this.savedScrollLeft, this.savedScrollTop);
1142				this.savedScrollLeft = this.savedScrollTop = false;
1143			}
1144
1145			if (this.editor.expanded)
1146				BX.bind(window, "scroll", BX.proxy(this.editor.PreventScroll, this.editor));
1147		}
1148	};
1149
1150	function ContextMenu(editor)
1151	{
1152		this.editor = editor;
1153		BX.addCustomEvent(this.editor, 'OnIframeContextMenu', BX.delegate(this.Show, this));
1154		this.Init();
1155	}
1156
1157	ContextMenu.prototype = {
1158		Init: function()
1159		{
1160			var
1161				_this = this,
1162				defaultItem = {
1163					TEXT: BX.message('ContMenuDefProps'),
1164					ACTION: function()
1165					{
1166						_this.editor.selection.SetBookmark(_this.savedRange);
1167						_this.editor.GetDialog('Default').Show(false, _this.savedRange);
1168						_this.Hide();
1169					}
1170				};
1171
1172			// Remove format ?
1173			// Replace Node by children ?
1174
1175			this.items = {
1176				// Surrogates
1177				'php' : [
1178					{
1179						TEXT: BX.message('BXEdContMenuPhpCode'),
1180						ACTION: function()
1181						{
1182							var items = _this.GetTargetItem();
1183							if (items && items.php)
1184							{
1185								_this.editor.GetDialog('Source').Show(items.php.bxTag);
1186							}
1187							_this.Hide();
1188						}
1189					}
1190				],
1191				'anchor' : [
1192					{
1193						TEXT: BX.message('BXEdEditAnchor'),
1194						ACTION: function()
1195						{
1196							var items = _this.GetTargetItem();
1197							if (items && items.anchor)
1198							{
1199								_this.editor.GetDialog('Anchor').Show(items.anchor.bxTag);
1200							}
1201							_this.Hide();
1202						}
1203					}
1204				],
1205				'javascript' : [
1206					{
1207						TEXT: BX.message('BXEdContMenuJavascript'),
1208						ACTION: function()
1209						{
1210							var items = _this.GetTargetItem();
1211							if (items && items.javascript)
1212							{
1213								_this.editor.GetDialog('Source').Show(items.javascript.bxTag);
1214							}
1215							_this.Hide();
1216						}
1217					}
1218				],
1219				'htmlcomment' : [
1220					{
1221						TEXT: BX.message('BXEdContMenuHtmlComment'),
1222						ACTION: function()
1223						{
1224							var items = _this.GetTargetItem();
1225							if (items && items.htmlcomment)
1226							{
1227								_this.editor.GetDialog('Source').Show(items.htmlcomment.bxTag);
1228							}
1229							_this.Hide();
1230						}
1231					}
1232				],
1233				'iframe' : [
1234					{
1235						TEXT: BX.message('BXEdContMenuIframe'),
1236						ACTION: function()
1237						{
1238							var items = _this.GetTargetItem();
1239							if (items && items.iframe)
1240							{
1241								_this.editor.GetDialog('Source').Show(items.iframe.bxTag);
1242							}
1243							_this.Hide();
1244						}
1245					}
1246				],
1247				'style' : [
1248					{
1249						TEXT: BX.message('BXEdContMenuStyle'),
1250						ACTION: function()
1251						{
1252							var items = _this.GetTargetItem();
1253							if (items && items.style)
1254							{
1255								_this.editor.GetDialog('Source').Show(items.style.bxTag);
1256							}
1257							_this.Hide();
1258						}
1259					}
1260				],
1261				'object' : [
1262					{
1263						TEXT: BX.message('BXEdContMenuObject'),
1264						ACTION: function()
1265						{
1266							var items = _this.GetTargetItem();
1267							if (items && items.object)
1268							{
1269								_this.editor.GetDialog('Source').Show(items.object.bxTag);
1270							}
1271							_this.Hide();
1272						}
1273					}
1274				],
1275				'component' : [
1276					{
1277						TEXT: BX.message('BXEdContMenuComponent'),
1278						ACTION: function()
1279						{
1280							var items = _this.GetTargetItem();
1281							if (items && items.component)
1282							{
1283								// Show dialog
1284								_this.editor.components.ShowPropertiesDialog(items.component.bxTag.params, _this.editor.GetBxTag(items.component.bxTag.surrogateId));
1285							}
1286							_this.Hide();
1287						}
1288					},
1289					{
1290						TEXT: BX.message('BXEdContMenuComponentRemove'),
1291						ACTION: function()
1292						{
1293							var items = _this.GetTargetItem();
1294							if (items && items.component)
1295							{
1296								BX.remove(items.component.element);
1297							}
1298							_this.Hide();
1299						}
1300					}
1301				],
1302				'printbreak' : [
1303					{
1304						TEXT: BX.message('NodeRemove'),
1305						ACTION: function(e)
1306						{
1307							var node = _this.GetTargetItem('printbreak');
1308							if (node && node.element)
1309							{
1310								_this.editor.selection.RemoveNode(node.element);
1311							}
1312							_this.Hide();
1313						}
1314					}
1315				],
1316				'video': [
1317					{
1318						TEXT: BX.message('BXEdVideoProps'),
1319						bbMode: true,
1320						ACTION: function()
1321						{
1322							var node = _this.GetTargetItem('video');
1323							if (node)
1324							{
1325								_this.editor.GetDialog('Video').Show(node.bxTag);
1326							}
1327							_this.Hide();
1328						}
1329					},
1330					{
1331						TEXT: BX.message('BXEdVideoDel'),
1332						bbMode: true,
1333						ACTION: function(e)
1334						{
1335							var node = _this.GetTargetItem('video');
1336							if (node && node.element)
1337							{
1338								_this.editor.selection.RemoveNode(node.element);
1339							}
1340							_this.Hide();
1341						}
1342					}
1343				],
1344				'smile' : [],
1345
1346				// Nodes
1347				'A' : [
1348					{
1349						TEXT: BX.message('ContMenuLinkEdit'),
1350						bbMode: true,
1351						ACTION: function()
1352						{
1353							var node = _this.GetTargetItem('A');
1354							if (node)
1355							{
1356								_this.editor.GetDialog('Link').Show([node], this.savedRange);
1357							}
1358							_this.Hide();
1359						}
1360					},
1361					{
1362						TEXT: BX.message('ContMenuLinkDel'),
1363						bbMode: true,
1364						ACTION: function()
1365						{
1366							var link = _this.GetTargetItem('A');
1367							if (link && _this.editor.action.IsSupported('removeLink'))
1368							{
1369								_this.editor.action.Exec('removeLink', [link]);
1370							}
1371							_this.Hide();
1372						}
1373					}
1374				],
1375				'IMG': [
1376					{
1377						TEXT: BX.message('ContMenuImgEdit'),
1378						bbMode: true,
1379						ACTION: function()
1380						{
1381							var node = _this.GetTargetItem('IMG');
1382							if (node)
1383							{
1384								_this.editor.GetDialog('Image').Show([node], _this.savedRange);
1385							}
1386							_this.Hide();
1387						}
1388					},
1389					{
1390						TEXT: BX.message('ContMenuImgDel'),
1391						bbMode: true,
1392						ACTION: function()
1393						{
1394							var node = _this.GetTargetItem('IMG');
1395							if (node)
1396							{
1397								_this.editor.selection.RemoveNode(node);
1398							}
1399							_this.Hide();
1400						}
1401					}
1402				],
1403				'DIV': [
1404					{
1405						TEXT: BX.message('ContMenuCleanDiv'),
1406						title: BX.message('ContMenuCleanDiv_Title'),
1407						ACTION: function()
1408						{
1409							var node = _this.GetTargetItem('DIV');
1410							if (node)
1411							{
1412								_this.editor.On('OnHtmlContentChangedByControl');
1413								_this.editor.util.ReplaceWithOwnChildren(node);
1414								_this.editor.synchro.FullSyncFromIframe();
1415							}
1416							_this.Hide();
1417						}
1418					},
1419					defaultItem
1420				],
1421				'TABLE': [
1422
1423					{
1424						TEXT: BX.message('BXEdTableInsertMenu'),
1425						HIDE_ITEM: function()
1426						{
1427							var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1428							return !cells || cells.length != 1;
1429						},
1430						MENU: [
1431							// Column
1432							{
1433								TEXT: BX.message('BXEdTableInsColLeft'),
1434								ACTION: function()
1435								{
1436									_this.editor.action.Exec('tableOperation', {
1437										actionType: 'insertColumnLeft',
1438										tableNode: _this.GetTargetItem('TABLE'),
1439										range: _this.savedRange
1440									});
1441									_this.Hide();
1442								}
1443							},
1444							{
1445								TEXT: BX.message('BXEdTableInsColRight'),
1446								ACTION: function()
1447								{
1448									_this.editor.action.Exec('tableOperation', {
1449										actionType: 'insertColumnRight',
1450										tableNode: _this.GetTargetItem('TABLE'),
1451										range: _this.savedRange
1452									});
1453									_this.Hide();
1454								}
1455							},
1456							// Row
1457							{
1458								TEXT: BX.message('BXEdTableInsRowUpper'),
1459								ACTION: function()
1460								{
1461									_this.editor.action.Exec('tableOperation', {
1462										actionType: 'insertRowUpper',
1463										tableNode: _this.GetTargetItem('TABLE'),
1464										range: _this.savedRange
1465									});
1466									_this.Hide();
1467								}
1468							},
1469							{
1470								TEXT: BX.message('BXEdTableInsRowLower'),
1471								ACTION: function()
1472								{
1473									_this.editor.action.Exec('tableOperation', {
1474										actionType: 'insertRowLower',
1475										tableNode: _this.GetTargetItem('TABLE'),
1476										range: _this.savedRange
1477									});
1478									_this.Hide();
1479								}
1480							},
1481							// Cell
1482							{
1483								TEXT: BX.message('BXEdTableInsCellBefore'),
1484								ACTION: function()
1485								{
1486									_this.editor.action.Exec('tableOperation', {
1487										actionType: 'insertCellLeft',
1488										tableNode: _this.GetTargetItem('TABLE'),
1489										range: _this.savedRange
1490									});
1491									_this.Hide();
1492								}
1493							},
1494							{
1495								TEXT: BX.message('BXEdTableInsCellAfter'),
1496								ACTION: function()
1497								{
1498									_this.editor.action.Exec('tableOperation', {
1499										actionType: 'insertCellRight',
1500										tableNode: _this.GetTargetItem('TABLE'),
1501										range: _this.savedRange
1502									});
1503									_this.Hide();
1504								}
1505							}
1506						]
1507					},
1508					{
1509						TEXT: BX.message('BXEdTableRemoveMenu'),
1510						MENU: [
1511							{
1512								TEXT: BX.message('BXEdTableDelCol'),
1513								ACTION: function()
1514								{
1515									_this.editor.action.Exec('tableOperation', {
1516										actionType: 'removeColumn',
1517										tableNode: _this.GetTargetItem('TABLE'),
1518										range: _this.savedRange
1519									});
1520									_this.Hide();
1521								},
1522								HIDE_ITEM: function()
1523								{
1524									var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1525									return !cells || cells.length != 1;
1526								}
1527							},
1528							{
1529								TEXT: BX.message('BXEdTableDelRow'),
1530								ACTION: function()
1531								{
1532									_this.editor.action.Exec('tableOperation', {
1533										actionType: 'removeRow',
1534										tableNode: _this.GetTargetItem('TABLE'),
1535										range: _this.savedRange
1536									});
1537									_this.Hide();
1538								},
1539								HIDE_ITEM: function()
1540								{
1541									var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1542									return !cells || cells.length != 1;
1543								}
1544							},
1545							{
1546								TEXT: BX.message('BXEdTableDellCell'),
1547								ACTION: function()
1548								{
1549									_this.editor.action.Exec('tableOperation', {
1550										actionType: 'removeCell',
1551										tableNode: _this.GetTargetItem('TABLE'),
1552										range: _this.savedRange
1553									});
1554									_this.Hide();
1555								},
1556								HIDE_ITEM: function()
1557								{
1558									var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1559									return !cells || cells.length != 1;
1560								}
1561							},
1562							{
1563								TEXT: BX.message('BXEdTableDellSelectedCells'),
1564								ACTION: function()
1565								{
1566									_this.editor.action.Exec('tableOperation', {
1567										actionType: 'removeSelectedCells',
1568										tableNode: _this.GetTargetItem('TABLE'),
1569										range: _this.savedRange
1570									});
1571									_this.Hide();
1572								},
1573								HIDE_ITEM: function()
1574								{
1575									var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1576									return !cells || cells.length === 1;
1577								}
1578							}
1579						]
1580					},
1581					{
1582						TEXT: BX.message('BXEdTableMergeMenu'),
1583						MENU: [
1584							{
1585								TEXT: BX.message('BXEdTableMergeSelectedCells'),
1586								ACTION: function()
1587								{
1588									_this.editor.action.Exec('tableOperation', {
1589										actionType: 'mergeSelectedCells',
1590										tableNode: _this.GetTargetItem('TABLE'),
1591										range: _this.savedRange
1592									});
1593									_this.Hide();
1594								},
1595								HIDE_ITEM: function()
1596								{
1597									if (!_this.savedRange.collapsed)
1598									{
1599										return !_this.editor.action.actions.tableOperation.canBeMerged(false, _this.savedRange, _this.GetTargetItem('TABLE'));
1600									}
1601
1602									return true;
1603								}
1604							},
1605							{
1606								TEXT: BX.message('BXEdTableMergeRight'),
1607								ACTION: function()
1608								{
1609									_this.editor.action.Exec('tableOperation', {
1610										actionType: 'mergeRightCell',
1611										tableNode: _this.GetTargetItem('TABLE'),
1612										range: _this.savedRange
1613									});
1614									_this.Hide();
1615								},
1616								HIDE_ITEM: function()
1617								{
1618									return !_this.editor.action.actions.tableOperation.canBeMergedWithRight(_this.savedRange, _this.GetTargetItem('TABLE'));
1619								}
1620							},
1621							{
1622								TEXT: BX.message('BXEdTableMergeBottom'),
1623								ACTION: function()
1624								{
1625									_this.editor.action.Exec('tableOperation', {
1626										actionType: 'mergeBottomCell',
1627										tableNode: _this.GetTargetItem('TABLE'),
1628										range: _this.savedRange
1629									});
1630									_this.Hide();
1631								},
1632								HIDE_ITEM: function()
1633								{
1634									return !_this.editor.action.actions.tableOperation.canBeMergedWithBottom(_this.savedRange, _this.GetTargetItem('TABLE'));
1635								}
1636							},
1637							{
1638								TEXT: BX.message('BXEdTableMergeRowCells'),
1639								ACTION: function()
1640								{
1641									_this.editor.action.Exec('tableOperation', {
1642										actionType: 'mergeRow',
1643										tableNode: _this.GetTargetItem('TABLE'),
1644										range: _this.savedRange
1645									});
1646									_this.Hide();
1647								},
1648								HIDE_ITEM: function()
1649								{
1650									var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1651									return !cells || cells.length > 1;
1652								}
1653							},
1654							{
1655								TEXT: BX.message('BXEdTableMergeColCells'),
1656								ACTION: function()
1657								{
1658									_this.editor.action.Exec('tableOperation', {
1659										actionType: 'mergeColumn',
1660										tableNode: _this.GetTargetItem('TABLE'),
1661										range: _this.savedRange
1662									});
1663									_this.Hide();
1664								},
1665								HIDE_ITEM: function()
1666								{
1667									var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1668									return !cells || cells.length > 1;
1669								}
1670							}
1671						]
1672					},
1673					{
1674						TEXT: BX.message('BXEdTableSplitMenu'),
1675						HIDE_ITEM: function()
1676						{
1677							var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1678							return !cells || cells.length != 1;
1679						},
1680						MENU: [
1681							{
1682								TEXT: BX.message('BXEdTableSplitCellHor'),
1683								ACTION: function()
1684								{
1685									_this.editor.action.Exec('tableOperation', {
1686										actionType: 'splitHorizontally',
1687										tableNode: _this.GetTargetItem('TABLE'),
1688										range: _this.savedRange
1689									});
1690									_this.Hide();
1691								}
1692							},
1693							{
1694								TEXT: BX.message('BXEdTableSplitCellVer'),
1695								ACTION: function()
1696								{
1697									_this.editor.action.Exec('tableOperation', {
1698										actionType: 'splitVertically',
1699										tableNode: _this.GetTargetItem('TABLE'),
1700										range: _this.savedRange
1701									});
1702									_this.Hide();
1703								}
1704							}
1705						]
1706					},
1707					{SEPARATOR: true},
1708
1709					{
1710						TEXT: BX.message('BXEdTableTableCellProps'),
1711						ACTION: function()
1712						{
1713							var node = _this.GetTargetItem('TABLE');
1714							if (node)
1715							{
1716								var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1717								_this.editor.GetDialog('Default').Show(cells, _this.savedRange);
1718							}
1719							_this.Hide();
1720						},
1721						HIDE_ITEM: function()
1722						{
1723							var cells = _this.editor.action.actions.tableOperation.getSelectedCells(_this.savedRange, _this.GetTargetItem('TABLE'));
1724							return !cells || cells.length != 1;
1725						}
1726					},
1727
1728					{
1729						TEXT: BX.message('BXEdTableTableProps'),
1730						ACTION: function()
1731						{
1732							var node = _this.GetTargetItem('TABLE');
1733							if (node)
1734							{
1735								_this.editor.GetDialog('Table').Show([node], _this.savedRange);
1736							}
1737							_this.Hide();
1738						}
1739					},
1740					{
1741						TEXT: BX.message('BXEdTableDeleteTable'),
1742						bbMode: true,
1743						ACTION: function()
1744						{
1745							var node = _this.GetTargetItem('TABLE');
1746							if (node)
1747							{
1748								_this.editor.selection.RemoveNode(node);
1749							}
1750							_this.Hide();
1751						}
1752					}
1753				],
1754				// ...
1755				'DEFAULT': [defaultItem]
1756			};
1757		},
1758
1759		Show: function(e, target, collapsedSelection)
1760		{
1761			//open context menu only in admin html-editor
1762			const targetBxTag = this.editor.GetBxTag(target);
1763			if (targetBxTag.tag === 'diskfile0' || targetBxTag.hideContextMenu)
1764			{
1765				return;
1766			}
1767
1768			this.savedRange = this.editor.selection.GetBookmark();
1769			this.Hide();
1770
1771			this.editor.contextMenuShown = true;
1772			if (this.contextMenuShownTimeout)
1773			{
1774				this.contextMenuShownTimeout = clearTimeout(this.contextMenuShownTimeout);
1775			}
1776			this.nodes = [];
1777			this.tagIndex = {};
1778			var
1779				bxTag,
1780				i, j, k, menuItems, item, itemGr,
1781				arItems = [],
1782				maxIter = 20, iter = 0,
1783				element = target,
1784				label;
1785
1786			this.targetItems = {};
1787			while (true)
1788			{
1789				if (element.nodeName && element.nodeName.toUpperCase() != 'BODY')
1790				{
1791					if (element.nodeType != 3)
1792					{
1793						bxTag = this.editor.GetBxTag(element);
1794
1795						if (bxTag && bxTag.tag == 'surrogate_dd')
1796						{
1797							var origTag = this.editor.GetBxTag(bxTag.params.origId);
1798							element = this.editor.GetIframeElement(origTag.id);
1799							this.PushTargetItem(origTag.tag, {element: element, bxTag: origTag});
1800							this.nodes = [element];
1801							this.tagIndex[origTag.tag] = 0;
1802							iter = 0;
1803							element = element.parentNode;
1804							continue;
1805						}
1806						else if (bxTag && bxTag.tag && this.items[bxTag.tag])
1807						{
1808							this.nodes = [element];
1809							this.PushTargetItem(bxTag.tag, {element: element, bxTag: bxTag.tag});
1810							this.nodes = [element];
1811							this.tagIndex[bxTag.tag] = 0;
1812							iter = 0;
1813							element = element.parentNode;
1814							continue;
1815						}
1816
1817						label = element.nodeName;
1818						this.PushTargetItem(label, element);
1819						this.nodes.push(element);
1820						this.tagIndex[label] = this.nodes.length - 1;
1821					}
1822					iter++;
1823				}
1824
1825				if (!element ||
1826					element.nodeName && element.nodeName.toUpperCase() == 'BODY' ||
1827					iter >= maxIter)
1828				{
1829					break;
1830				}
1831
1832				element = element.parentNode;
1833			}
1834
1835			for (i in this.items)
1836			{
1837				if (this.items.hasOwnProperty(i) && this.tagIndex[i] != undefined)
1838				{
1839					if (arItems.length > 0)
1840					{
1841						arItems.push({SEPARATOR : true});
1842					}
1843
1844					for (j = 0; j < this.items[i].length; j++)
1845					{
1846						if (typeof this.items[i][j].HIDE_ITEM == 'function'  && this.items[i][j].HIDE_ITEM() === true)
1847							continue;
1848
1849						if (this.editor.bbCode && !this.items[i][j].bbMode)
1850							continue;
1851
1852						if (this.items[i][j].MENU)
1853						{
1854							itemGr = BX.clone(this.items[i][j]);
1855							menuItems = [];
1856							for (k = 0; k < itemGr.MENU.length; k++)
1857							{
1858								item = itemGr.MENU[k];
1859								if (typeof item.HIDE_ITEM == 'function'  && item.HIDE_ITEM() === true)
1860									continue;
1861
1862								if (this.editor.bbCode && !item.bbMode)
1863									continue;
1864
1865								menuItems.push(item);
1866							}
1867
1868							if (menuItems.length === 0)
1869								continue;
1870
1871							itemGr.MENU = menuItems;
1872
1873							arItems.push(itemGr);
1874						}
1875						else
1876						{
1877							arItems.push(this.items[i][j]);
1878						}
1879					}
1880				}
1881			}
1882
1883			if (arItems.length == 0 && (!this.editor.bbCode || this.items['DEFAULT'].bbMode))
1884			{
1885				if (!this.savedRange || (!this.savedRange.collapsed && !collapsedSelection))
1886				{
1887					for (j = 0; j < this.items['DEFAULT'].length; j++)
1888					{
1889						arItems.push(this.items['DEFAULT'][j]);
1890					}
1891				}
1892			}
1893
1894			var
1895				x = e.clientX,
1896				y = e.clientY;
1897
1898			if (!this.dummyTarget)
1899			{
1900				this.dummyTarget = this.editor.dom.iframeCont.appendChild(BX.create('DIV', {props: {className: 'bxhtmled-dummy-target'}}));
1901			}
1902
1903			this.dummyTarget.style.left = x + 'px';
1904			this.dummyTarget.style.top = y + 'px';
1905			this.dummyTarget.style.zIndex = '2002';
1906
1907			if (arItems.length > 0)
1908			{
1909				this.OPENER = new BX.COpener({
1910					DIV: this.dummyTarget,
1911					MENU: arItems,
1912					TYPE: 'click',
1913					ACTIVE_CLASS: 'adm-btn-active',
1914					CLOSE_ON_CLICK: true
1915				});
1916				this.OPENER.Open();
1917				var popupDiv = this.OPENER.GetMenu().DIV;
1918				popupDiv.id = 'bx-admin-prefix';
1919				BX.addClass(popupDiv, 'bx-core-popup-menu-editor');
1920
1921				this.isOpened = true;
1922				BX.addCustomEvent(this.editor, 'OnIframeClick', BX.proxy(this.Hide, this));
1923				BX.addCustomEvent(this.editor, 'OnIframeKeyup', BX.proxy(this.CheckEscapeClose, this));
1924				return BX.PreventDefault(e);
1925			}
1926		},
1927
1928		Hide: function()
1929		{
1930			if (this.OPENER)
1931			{
1932				var _this = this;
1933				this.contextMenuShownTimeout = setTimeout(function(){_this.editor.contextMenuShown = false;}, 300);
1934				this.OPENER.bMenuInit = true;
1935				this.OPENER.Close();
1936				this.isOpened = false;
1937				BX.removeCustomEvent(this.editor, 'OnIframeClick', BX.proxy(this.Hide, this));
1938				BX.removeCustomEvent(this.editor, 'OnIframeKeyup', BX.proxy(this.CheckEscapeClose, this));
1939			}
1940		},
1941
1942		CheckEscapeClose: function(e, keyCode)
1943		{
1944			if (keyCode == this.editor.KEY_CODES['escape'])
1945				this.Hide();
1946		},
1947
1948		GetTargetItem: function(tag)
1949		{
1950			return tag ? (this.targetItems[tag] || null) : this.targetItems;
1951		},
1952
1953		PushTargetItem: function(key, tag)
1954		{
1955			if (!this.targetItems[key])
1956				this.targetItems[key] = tag;
1957		}
1958	};
1959
1960	function Toolbar(editor, topControls)
1961	{
1962		this.editor = editor;
1963		this.pCont = editor.dom.toolbar;
1964		this.controls = {};
1965		this.bCompact = false;
1966		this.topControls = topControls;
1967		this.showMoreButton = false;
1968		this.shown = true;
1969		this.height = null;
1970		this.Init();
1971	}
1972
1973	Toolbar.prototype = {
1974		Init: function()
1975		{
1976			this.BuildControls();
1977			// Init Event handlers
1978			BX.addCustomEvent(this.editor, "OnIframeFocus", BX.delegate(this.EnableWysiwygButtons, this));
1979			BX.addCustomEvent(this.editor, "OnTextareaFocus", BX.delegate(this.DisableWysiwygButtons, this));
1980			BX.bind(this.pCont, 'scroll', BX.proxy(this.SetScrollShadows, this));
1981		},
1982
1983		SetScrollShadows: function()
1984		{
1985			const scrollWidth = this.pCont.scrollWidth;
1986			const width = this.pCont.offsetWidth;
1987			let scrollLeft = this.pCont.scrollLeft;
1988			let scrollRight = scrollWidth - width - scrollLeft;
1989			if (scrollRight < 0)
1990			{
1991				scrollLeft += scrollRight;
1992				scrollRight = 0;
1993			}
1994			if (scrollLeft > 20 && scrollRight > 20)
1995			{
1996				this.pCont.parentNode.classList.remove('has-tools-on-left');
1997				this.pCont.parentNode.classList.remove('has-tools-on-right');
1998				this.pCont.parentNode.classList.add('has-tools-on-sides');
1999			}
2000			else if (scrollLeft > 20)
2001			{
2002				this.pCont.parentNode.classList.add('has-tools-on-left');
2003				this.pCont.parentNode.classList.remove('has-tools-on-right');
2004				this.pCont.parentNode.classList.remove('has-tools-on-sides');
2005			}
2006			else if (scrollRight > 20)
2007			{
2008				this.pCont.parentNode.classList.remove('has-tools-on-left');
2009				this.pCont.parentNode.classList.add('has-tools-on-right');
2010				this.pCont.parentNode.classList.remove('has-tools-on-sides');
2011			}
2012			else if (scrollLeft < 20 && scrollRight < 20)
2013			{
2014				this.pCont.parentNode.classList.remove('has-tools-on-left');
2015				this.pCont.parentNode.classList.remove('has-tools-on-right');
2016				this.pCont.parentNode.classList.remove('has-tools-on-sides');
2017			}
2018		},
2019
2020		BuildControls: function()
2021		{
2022			BX.cleanNode(this.pCont);
2023			var
2024				i,
2025				wrap, moreCont, cont,
2026				map = this.GetControlsMap(),
2027				wraps = {
2028					left: this.pCont.appendChild(BX.create('span', {props: {className: 'bxhtmled-top-bar-left-wrap'}, style: {display: 'none'}})),
2029					main: this.pCont.appendChild(BX.create('span', {props: {className: 'bxhtmled-top-bar-wrap'}, style: {display: 'none'}})),
2030					right: this.pCont.appendChild(BX.create('span', {props: {className: 'bxhtmled-top-bar-right-wrap'}, style: {display: 'none'}})),
2031					hidden: this.pCont.appendChild(BX.create('span', {props: {className: 'bxhtmled-top-bar-hidden-wrap'}}))
2032				};
2033
2034			this.hiddenWrap = wraps.hidden;
2035
2036			this.editor.normalWidth = this.editor.NORMAL_WIDTH;
2037			for (i = 0; i < map.length; i++)
2038			{
2039				if(map[i].hidden)
2040				{
2041					map[i].wrap = 'hidden';
2042					this.showMoreButton = true;
2043				}
2044				else if (map[i].checkWidth && map[i].offsetWidth)
2045				{
2046					this.editor.normalWidth += map[i].offsetWidth;
2047				}
2048
2049				wrap = wraps[(map[i].wrap || 'main')];
2050
2051				if (!wrap)
2052				{
2053					// We trying to find wrap as dom element by Id
2054					wrap = BX(map[i].wrap);
2055					if (wrap)
2056					{
2057						wraps[map[i].wrap] = wrap;
2058					}
2059					else
2060					{
2061						wrap = wraps['main'];
2062					}
2063				}
2064
2065				if (wrap.style.display == 'none')
2066					wrap.style.display = '';
2067
2068				if (map[i].separator)
2069				{
2070					wrap.appendChild(this.GetSeparator()); // Show separator
2071				}
2072				else if(this.topControls[map[i].id])
2073				{
2074					if (!this.controls[map[i].id])
2075					{
2076						this.controls[map[i].id] = new this.topControls[map[i].id](this.editor, wrap);
2077					}
2078					else
2079					{
2080						cont = this.controls[map[i].id].GetPopupBindCont ? this.controls[map[i].id].GetPopupBindCont() : this.controls[map[i].id].GetCont();
2081
2082						if (this.controls[map[i].id].CheckBeforeShow && !this.controls[map[i].id].CheckBeforeShow())
2083							continue;
2084
2085						if (this.controls.More && ((this.bCompact && !map[i].compact) || map[i].hidden))
2086						{
2087							if (!moreCont)
2088							{
2089								moreCont = this.controls.More.GetPopupCont();
2090							}
2091							moreCont.appendChild(cont);
2092						}
2093						else
2094						{
2095							wrap.appendChild(cont);
2096						}
2097					}
2098				}
2099			}
2100
2101			for (i in wraps)
2102			{
2103				if (wraps.hasOwnProperty(i) && i !== 'main' && i !== 'left' && i !== 'right' && i !== 'hidden' && wraps[i].getAttribute('data-bx-check-command') !== 'N')
2104				{
2105					wraps[i].setAttribute('data-bx-check-command', 'N');
2106					BX.bind(wraps[i], "click", BX.proxy(function(e)
2107					{
2108						this.editor.CheckCommand(e.target || e.srcElement);
2109					}, this));
2110				}
2111			}
2112		},
2113
2114		GetControlsMap: function()
2115		{
2116			if (this.controlsMap)
2117				return this.controlsMap;
2118
2119			var res = this.editor.config.controlsMap;
2120			if (!res)
2121			{
2122				res = [
2123					//{id: 'SearchButton', wrap: 'left', compact: true},
2124					{id: 'ChangeView', wrap: 'left', compact: true, sort: 10},
2125					{id: 'placeholder_selector',  compact: true, sort: 15},
2126					{id: 'Undo', compact: false, sort: 20},
2127					{id: 'Redo', compact: false, sort: 30},
2128					{id: 'StyleSelector', compact: true, sort: 40},
2129					{id: 'FontSelector', compact: false, sort: 50},
2130					{id: 'FontSize', compact: false, sort: 60},
2131					{separator: true, compact: false, sort: 70},
2132					{id: 'Bold', compact: true, sort: 80},
2133					{id: 'Italic', compact: true, sort: 90},
2134					{id: 'Underline', compact: true, sort: 100},
2135					{id: 'Strikeout', compact: true, sort: 110},
2136					{id: 'RemoveFormat', compact: true, sort: 120},
2137					{id: 'Color', compact: true, sort: 130},
2138					{separator: true, compact: false, sort: 140},
2139					{id: 'OrderedList', compact: true, sort: 150},
2140					{id: 'UnorderedList', compact: true, sort: 160},
2141					{id: 'IndentButton', compact: true, sort: 170},
2142					{id: 'OutdentButton', compact: true, sort: 180},
2143					{id: 'AlignList',compact: true, sort: 190},
2144					{separator: true, compact: false, sort: 200},
2145					{id: 'InsertLink', compact: true, sort: 210},
2146					{id: 'InsertImage', compact: true, sort: 220},
2147					{id: 'InsertVideo', compact: true, sort: 230},
2148					{id: 'InsertAnchor', compact: false, sort: 240},
2149					{id: 'InsertTable', compact: false, sort: 250},
2150					{id: 'InsertChar', compact: false, hidden: true, sort: 260},
2151					{id: 'PrintBreak', compact: false, hidden: true, sort: 270},
2152					{id: 'PageBreak', compact: false, hidden: true, sort: 275},
2153					{id: 'Spellcheck', compact: false, hidden: true, sort: 280},
2154					{id: 'InsertHr', compact: false, hidden: true, sort: 290},
2155					{id: 'Sub', compact: false, hidden: true, sort: 310},
2156					{id: 'Sup', compact: false, hidden: true, sort: 320},
2157					{id: 'TemplateSelector', compact: false, sort: 330},
2158					{id: 'Fullscreen', compact: true, sort: 340},
2159
2160					{id: 'More', compact: true, sort: 400},
2161					{id: 'Settings',  wrap: 'right', compact: true, sort: 500}
2162				];
2163			}
2164
2165			this.editor.On("GetControlsMap", [res]);
2166			res = res.sort(function(a, b){return a.sort - b.sort});
2167			this.controlsMap = res;
2168			return res;
2169		},
2170
2171		GetSeparator: function()
2172		{
2173			return BX.create('span', {props: {className: 'bxhtmled-top-bar-separator'}});
2174		},
2175
2176		GetHeight: function()
2177		{
2178			var res = 0;
2179			if (this.shown)
2180			{
2181				if (!this.height)
2182				{
2183					this.height = parseInt(this.editor.dom.toolbarCont.offsetHeight);
2184				}
2185				if (this.height === 0)
2186				{
2187					this.height = parseInt(getComputedStyle(this.editor.dom.toolbarCont).height);
2188				}
2189
2190				res = this.height;
2191			}
2192			return res;
2193		},
2194
2195		DisableWysiwygButtons: function(bDisable)
2196		{
2197			bDisable = bDisable !== false;
2198			for (var i in this.controls)
2199			{
2200				if (this.controls.hasOwnProperty(i) && typeof this.controls[i].Disable == 'function' && this.controls[i].disabledForTextarea !== false)
2201					this.controls[i].Disable(bDisable);
2202			}
2203		},
2204
2205		ShowControl: function(controlName)
2206		{
2207			if (this.controls[controlName])
2208			{
2209				this.controls[controlName].pCont.style.display = '';
2210			}
2211		},
2212
2213		HideControl: function(controlName)
2214		{
2215			if (this.controls[controlName])
2216			{
2217				this.controls[controlName].pCont.style.display = 'none';
2218			}
2219		},
2220
2221		EnableWysiwygButtons: function()
2222		{
2223			this.DisableWysiwygButtons(false);
2224		},
2225
2226		AdaptControls: function(width)
2227		{
2228			this.SetScrollShadows();
2229			var bCompact = width < this.editor.normalWidth;
2230			if (this.controls.More)
2231			{
2232				if (bCompact || this.showMoreButton)
2233				{
2234					this.controls.More.GetCont().style.display = '';
2235				}
2236				else
2237				{
2238					this.controls.More.GetCont().style.display = 'none';
2239				}
2240
2241				if (this.controls.More.pCont && this.controls.More.pCont.style.display !== 'none')
2242					this.controls.More.Close();
2243			}
2244
2245			if (!bCompact && this.showMoreButton)
2246			{
2247				var moreCont = this.controls.More.GetPopupCont();
2248				while (this.hiddenWrap.firstChild)
2249				{
2250					moreCont.appendChild(this.hiddenWrap.firstChild);
2251				}
2252			}
2253
2254			if (this.bCompact != bCompact)
2255			{
2256				this.bCompact = bCompact;
2257				this.BuildControls();
2258			}
2259		},
2260
2261		Hide: function()
2262		{
2263			this.shown = false;
2264			this.editor.dom.toolbarCont.style.display = 'none';
2265			this.editor.ResizeSceleton();
2266		},
2267
2268		Show: function()
2269		{
2270			this.shown = true;
2271			this.editor.dom.toolbarCont.style.display = '';
2272			this.editor.ResizeSceleton();
2273		},
2274
2275		IsShown: function()
2276		{
2277			return this.shown;
2278		}
2279	};
2280
2281	function NodeNavi(editor)
2282	{
2283		this.editor = editor;
2284		this.bShown = false;
2285		this.pCont = editor.dom.navCont;
2286		this.controls = {};
2287		this.height = 28;
2288		this.Init();
2289	}
2290
2291	NodeNavi.prototype = {
2292		Init: function()
2293		{
2294			BX.addCustomEvent(this.editor, "OnIframeMouseDown", BX.proxy(this.OnIframeMousedown, this));
2295			BX.addCustomEvent(this.editor, "OnIframeKeyup", BX.proxy(this.OnIframeKeyup, this));
2296			BX.addCustomEvent(this.editor, "OnTextareaFocus", BX.delegate(this.Disable, this));
2297			BX.addCustomEvent(this.editor, "OnHtmlContentChangedByControl", BX.delegate(this.OnIframeKeyup, this));
2298			BX.bind(this.pCont, 'click', BX.delegate(this.ShowMenu, this));
2299
2300			var _this = this;
2301
2302			this.items = {
2303				// Surrogates
2304				'php' : function(node, bxTag)
2305				{
2306					_this.editor.GetDialog('Source').Show(bxTag);
2307				},
2308				'anchor' : function(node, bxTag)
2309				{
2310					_this.editor.GetDialog('Anchor').Show(bxTag);
2311				},
2312				'javascript' : function(node, bxTag)
2313				{
2314					_this.editor.GetDialog('Source').Show(bxTag);
2315				},
2316				'htmlcomment' : function(node, bxTag)
2317				{
2318					_this.editor.GetDialog('Source').Show(bxTag);
2319				},
2320				'iframe' : function(node, bxTag)
2321				{
2322					_this.editor.GetDialog('Source').Show(bxTag);
2323				},
2324				'style' : function(node, bxTag)
2325				{
2326					_this.editor.GetDialog('Source').Show(bxTag);
2327				},
2328				'video' : function(node, bxTag)
2329				{
2330					_this.editor.GetDialog('Video').Show(bxTag);
2331				},
2332				'component' : function(node, bxTag)
2333				{
2334					_this.editor.components.ShowPropertiesDialog(bxTag.params, _this.editor.GetBxTag(bxTag.surrogateId));
2335				},
2336				'printbreak' : false,
2337
2338				// Nodes
2339				'A' : function(node)
2340				{
2341					_this.editor.GetDialog('Link').Show([node]);
2342				},
2343				'IMG' : function(node)
2344				{
2345					_this.editor.GetDialog('Image').Show([node]);
2346				},
2347				'TABLE' : function(node)
2348				{
2349					_this.editor.GetDialog('Table').Show([node]);
2350				},
2351				'DEFAULT' : function(node)
2352				{
2353					_this.editor.GetDialog('Default').Show([node]);
2354				}
2355			};
2356		},
2357
2358		Show: function(bShow)
2359		{
2360			this.bShown = bShow = bShow !== false;
2361			this.pCont.style.display = bShow ? 'block' : 'none';
2362		},
2363
2364		GetHeight: function()
2365		{
2366			if (!this.bShown)
2367				return 0;
2368
2369			if (!this.height)
2370				this.height = parseInt(this.pCont.offsetHeight);
2371
2372			return this.height;
2373		},
2374
2375		OnIframeMousedown: function(e, target, bxTag)
2376		{
2377			this.BuildNavi(target);
2378		},
2379
2380		OnIframeKeyup: function(e, keyCode, target)
2381		{
2382			this.BuildNavi(target);
2383		},
2384
2385		BuildNavi: function(node)
2386		{
2387			BX.cleanNode(this.pCont);
2388			if (!node)
2389			{
2390				node = this.editor.GetIframeDoc().body;
2391			}
2392			this.nodeIndex = [];
2393			var itemCont, label, bxTag;
2394			while (node)
2395			{
2396				if (node.nodeType != 3)
2397				{
2398					bxTag = this.editor.GetBxTag(node);
2399					if (bxTag.tag)
2400					{
2401						if (bxTag.tag == "surrogate_dd")
2402						{
2403							node = node.parentNode;
2404							continue;
2405						}
2406
2407						BX.cleanNode(this.pCont);
2408						this.nodeIndex = [];
2409
2410						label = bxTag.name || bxTag.tag;
2411					}
2412					else
2413					{
2414						label = node.nodeName;
2415					}
2416
2417					itemCont = BX.create("SPAN", {props: {className: "bxhtmled-nav-item"}, text: label});
2418					itemCont.setAttribute('data-bx-node-ind', this.nodeIndex.length.toString());
2419
2420					this.nodeIndex.push({node: node, bxTag: bxTag.tag});
2421
2422					if (this.pCont.firstChild)
2423					{
2424						this.pCont.insertBefore(itemCont, this.pCont.firstChild);
2425						if(!this.AdjustSize())
2426						{
2427							break;
2428						}
2429					}
2430					else
2431					{
2432						this.pCont.appendChild(itemCont);
2433					}
2434				}
2435				if (node.nodeName && node.nodeName.toUpperCase() == 'BODY')
2436				{
2437					break;
2438				}
2439				node = node.parentNode;
2440			}
2441
2442			this.AdjustSize();
2443		},
2444
2445		AdjustSize: function()
2446		{
2447			if (this.pCont.lastChild && this.pCont.lastChild.offsetTop > 0)
2448			{
2449				BX.remove(this.pCont.firstChild);
2450				return false;
2451			}
2452			return true;
2453		},
2454
2455		ShowMenu: function(e)
2456		{
2457			if (!this.nodeIndex)
2458			{
2459				return;
2460			}
2461
2462			var
2463				_this = this,
2464				nodeIndex,
2465				origNode,
2466				target;
2467
2468			if (e.target)
2469			{
2470				target = e.target;
2471			}
2472			else if (e.srcElement)
2473			{
2474				target = e.srcElement;
2475			}
2476			if (target.nodeType == 3)
2477			{
2478				target = target.parentNode;
2479			}
2480
2481			if (target)
2482			{
2483				nodeIndex = target.getAttribute('data-bx-node-ind');
2484				if (!this.nodeIndex[nodeIndex])
2485				{
2486					target = BX.findParent(target, function(node)
2487					{
2488						return node == _this.pCont || (node.getAttribute && node.getAttribute('data-bx-node-ind') >= 0);
2489					}, this.pCont);
2490					nodeIndex = target.getAttribute('data-bx-node-ind')
2491				}
2492
2493				if (this.nodeIndex[nodeIndex])
2494				{
2495					var id = 'bx_node_nav_' + Math.round(Math.random() * 1000000000);
2496					origNode = this.nodeIndex[nodeIndex].node;
2497
2498					var arItems = [];
2499					if (origNode.nodeName && origNode.nodeName.toUpperCase() != 'BODY')
2500					{
2501						if (!this.nodeIndex[nodeIndex].bxTag || !this.editor.phpParser.surrogateTags[this.nodeIndex[nodeIndex].bxTag])
2502						{
2503							arItems.push({
2504								text : BX.message('NodeSelect'),
2505								title : BX.message('NodeSelect'),
2506								className : "",
2507								onclick: function()
2508								{
2509									_this.editor.action.Exec('selectNode', origNode);
2510									this.popupWindow.close();
2511									this.popupWindow.destroy();
2512								}
2513							});
2514						}
2515
2516						arItems.push({
2517							text : BX.message('NodeRemove'),
2518							title : BX.message('NodeRemove'),
2519							className : "",
2520							onclick: function()
2521							{
2522								if (origNode && origNode.parentNode)
2523								{
2524									_this.BuildNavi(origNode.parentNode);
2525									_this.editor.selection.RemoveNode(origNode);
2526								}
2527								this.popupWindow.close();
2528								this.popupWindow.destroy();
2529							}
2530						});
2531
2532						var showProps = !(this.nodeIndex[nodeIndex] && this.nodeIndex[nodeIndex].bxTag && this.items[this.nodeIndex[nodeIndex].bxTag] == false);
2533						if (showProps)
2534						{
2535							arItems.push({
2536								text : BX.message('NodeProps'),
2537								title : BX.message('NodeProps'),
2538								className : "",
2539								onclick: function()
2540								{
2541									_this.ShowNodeProperties(origNode);
2542									this.popupWindow.close();
2543									this.popupWindow.destroy();
2544								}
2545							});
2546						}
2547					}
2548					else
2549					{
2550						arItems = [
2551							{
2552								text : BX.message('NodeSelectBody'),
2553								title : BX.message('NodeSelectBody'),
2554								className : "",
2555								onclick: function()
2556								{
2557									_this.editor.iframeView.CheckContentLastChild();
2558									_this.editor.action.Exec('selectNode', origNode);
2559									_this.editor.Focus();
2560									this.popupWindow.close();
2561									this.popupWindow.destroy();
2562								}
2563							},
2564							{
2565								text : BX.message('NodeRemoveBodyContent'),
2566								title : BX.message('NodeRemoveBodyContent'),
2567								className : "",
2568								onclick: function()
2569								{
2570									_this.BuildNavi(origNode);
2571									_this.editor.On('OnHtmlContentChangedByControl');
2572									_this.editor.iframeView.Clear();
2573									_this.editor.util.Refresh(origNode);
2574									_this.editor.synchro.FullSyncFromIframe();
2575									_this.editor.Focus();
2576
2577									this.popupWindow.close();
2578									this.popupWindow.destroy();
2579								}
2580							}
2581						];
2582					}
2583
2584
2585					BX.PopupMenu.show(id + "_menu", target, arItems, {
2586							overlay: {opacity: 1},
2587							events: {
2588								onPopupClose: function()
2589								{
2590									//BX.removeClass(this.bindElement, "bxec-add-more-over");
2591								}
2592							},
2593							offsetLeft: 1,
2594							zIndex: 4000,
2595							bindOptions: { position: "top" }
2596						}
2597					);
2598				}
2599			}
2600		},
2601
2602		ShowNodeProperties: function(node)
2603		{
2604			var bxTag, key;
2605			if (node.nodeName && node.nodeType == 1)
2606			{
2607				bxTag = this.editor.GetBxTag(node);
2608				key = bxTag.tag ? bxTag.tag : node.nodeName;
2609
2610				if (this.items[key] && typeof this.items[key] == 'function')
2611				{
2612					this.items[key](node, bxTag);
2613				}
2614				else
2615				{
2616					this.items['DEFAULT'](node, bxTag);
2617				}
2618			}
2619		},
2620
2621		// TODO: hide it ??
2622		Disable: function()
2623		{
2624			this.BuildNavi(false);
2625		},
2626
2627		Enable: function()
2628		{
2629		}
2630	};
2631
2632	function Overlay(editor, params)
2633	{
2634		this.editor = editor;
2635		this.id = 'bxeditor_overlay' + this.editor.id;
2636		this.zIndex = params && params.zIndex ? params.zIndex : 1001;
2637	}
2638
2639	Overlay.prototype =
2640	{
2641		Create: function ()
2642		{
2643			this.bCreated = true;
2644			this.bShown = false;
2645			var ws = BX.GetWindowScrollSize();
2646			this.pWnd = document.body.appendChild(BX.create("DIV", {props: {id: this.id, className: "bxhtmled-overlay"}, style: { width: ws.scrollWidth + "px", height: ws.scrollHeight + "px"}}));
2647			this.pWnd.ondrag = BX.False;
2648			this.pWnd.onselectstart = BX.False;
2649		},
2650
2651		Show: function(arParams)
2652		{
2653			if (!this.bCreated)
2654				this.Create();
2655			this.bShown = true;
2656			if (this.shownTimeout)
2657			{
2658				this.shownTimeout = clearTimeout(this.shownTimeout);
2659			}
2660			var ws = BX.GetWindowScrollSize();
2661			this.pWnd.style.display = 'block';
2662			this.pWnd.style.width = ws.scrollWidth + "px";
2663			this.pWnd.style.height = ws.scrollHeight + "px";
2664
2665			if (!arParams)
2666			{
2667				arParams = {};
2668			}
2669
2670			this.pWnd.style.zIndex = arParams.zIndex || this.zIndex;
2671
2672			BX.bind(window, "resize", BX.proxy(this.Resize, this));
2673			return this.pWnd;
2674		},
2675
2676		Hide: function ()
2677		{
2678			if (!this.bShown)
2679			{
2680				return;
2681			}
2682			var _this = this;
2683			_this.shownTimeout = setTimeout(function(){_this.bShown = false;}, 300);
2684			this.pWnd.style.display = 'none';
2685			BX.unbind(window, "resize", BX.proxy(this.Resize, this));
2686			this.pWnd.onclick = null;
2687		},
2688
2689		Resize: function ()
2690		{
2691			if (this.bCreated)
2692			{
2693				var ws = BX.GetWindowScrollSize();
2694				this.pWnd.style.width = ws.scrollWidth + "px";
2695				this.pWnd.style.height = ws.scrollHeight + "px";
2696			}
2697		}
2698	}
2699
2700	function Button(editor)
2701	{
2702		this.editor = editor;
2703		this.className = 'bxhtmled-top-bar-btn';
2704		this.activeClassName = 'bxhtmled-top-bar-btn-active';
2705		this.disabledClassName = 'bxhtmled-top-bar-btn-disabled';
2706		this.checkableAction = true;
2707		this.disabledForTextarea = true;
2708	}
2709
2710	Button.prototype = {
2711		Create: function ()
2712		{
2713			this.pCont = BX.create("SPAN", {props: {className: this.className, title: this.title || ''}, html: '<i></i>'});
2714			BX.bind(this.pCont, "click", BX.delegate(this.OnClick, this));
2715			BX.bind(this.pCont, "mousedown", BX.delegate(this.OnMouseDown, this));
2716			BX.bind(this.pCont, "dblclick", function(e){return BX.PreventDefault(e);});
2717
2718			if (this.action)
2719			{
2720				this.pCont.setAttribute('data-bx-type', 'action');
2721				this.pCont.setAttribute('data-bx-action', this.action);
2722				if (this.value)
2723					this.pCont.setAttribute('data-bx-value', this.value);
2724
2725				if (this.checkableAction)
2726				{
2727					this.editor.RegisterCheckableAction(this.action, {
2728						action: this.action,
2729						control: this,
2730						value: this.value
2731					});
2732				}
2733			}
2734		},
2735
2736		GetCont: function()
2737		{
2738			return this.pCont;
2739		},
2740
2741		Check: function (bFlag)
2742		{
2743			if(bFlag == this.checked || this.disabled)
2744				return;
2745
2746			this.checked = bFlag;
2747			if(this.checked)
2748			{
2749				BX.addClass(this.pCont, this.activeClassName);
2750			}
2751			else
2752			{
2753				BX.removeClass(this.pCont, this.activeClassName);
2754			}
2755		},
2756
2757		Disable: function(bFlag)
2758		{
2759			if(bFlag != this.disabled)
2760			{
2761				this.disabled = !!bFlag;
2762				if(bFlag)
2763				{
2764					if (this.action)
2765					{
2766						this.pCont.setAttribute('data-bx-type', '');
2767					}
2768					BX.addClass(this.pCont, this.disabledClassName);
2769				}
2770				else
2771				{
2772					if (this.action)
2773					{
2774						this.pCont.setAttribute('data-bx-type', 'action');
2775					}
2776					BX.removeClass(this.pCont, this.disabledClassName);
2777				}
2778			}
2779		},
2780
2781		OnClick: BX.DoNothing,
2782		OnMouseUp: function()
2783		{
2784			if(!this.checked)
2785			{
2786				BX.removeClass(this.pCont, this.activeClassName);
2787			}
2788			BX.unbind(document, 'mouseup', BX.proxy(this.OnMouseUp, this));
2789			BX.removeCustomEvent(this.editor, "OnIframeMouseUp", BX.proxy(this.OnMouseUp, this));
2790
2791			if (this.editor.toolbar && this.editor.toolbar.controls && this.editor.toolbar.controls.More)
2792			{
2793				this.editor.toolbar.controls.More.Close();
2794			}
2795		},
2796
2797		OnMouseDown: function()
2798		{
2799			if (!this.disabled)
2800			{
2801				if (this.disabledForTextarea || !this.editor.synchro.IsFocusedOnTextarea())
2802				{
2803					this.savedRange = this.editor.selection.SaveBookmark();
2804				}
2805				BX.addClass(this.pCont, this.activeClassName);
2806				BX.bind(document, 'mouseup', BX.proxy(this.OnMouseUp, this));
2807				BX.addCustomEvent(this.editor, "OnIframeMouseUp", BX.proxy(this.OnMouseUp, this));
2808			}
2809		},
2810
2811		GetValue: function()
2812		{
2813			return !!this.checked;
2814		},
2815
2816		SetValue: function(value)
2817		{
2818			this.Check(value);
2819		}
2820	};
2821
2822	// List
2823	function DropDown(editor)
2824	{
2825		this.editor = editor;
2826		this.className = 'bxhtmled-top-bar-btn';
2827		this.activeClassName = 'bxhtmled-top-bar-btn-active';
2828		this.activeListClassName = 'bxhtmled-top-bar-btn-active';
2829		this.arValues = [];
2830		this.checkableAction = true;
2831		this.disabledForTextarea = true;
2832		this.posOffset = {top: 6, left: -4};
2833		this.zIndex = 3005;
2834	}
2835
2836	DropDown.prototype = {
2837		Create: function ()
2838		{
2839			this.pCont = BX.create("SPAN", {props: {className: this.className}, html: '<i></i>'});
2840			this.pValuesCont = BX.create("DIV", {props: {className: "bxhtmled-popup bxhtmled-dropdown-cont"}, html: '<div class="bxhtmled-popup-corner"></div>'});
2841			if (this.title)
2842			{
2843				this.pCont.title = this.title;
2844			}
2845
2846			if(this.zIndex)
2847			{
2848				this.pValuesCont.style.zIndex = this.zIndex;
2849			}
2850
2851			this.valueIndex = {};
2852			this.pValuesContWrap = this.pValuesCont.appendChild(BX.create("DIV"));
2853			var but, value, _this = this;
2854			for (var i = 0; i < this.arValues.length; i++)
2855			{
2856				value = this.arValues[i];
2857				but = this.pValuesContWrap.appendChild(BX.create("SPAN", {props: {title: value.title, className: value.className}, html: '<i></i>'}));
2858				but.setAttribute('data-bx-dropdown-value', value.id);
2859				this.valueIndex[value.id] = i;
2860
2861				if (value.action)
2862				{
2863					but.setAttribute('data-bx-type', 'action');
2864					but.setAttribute('data-bx-action', value.action);
2865					if (value.value)
2866					{
2867						but.setAttribute('data-bx-value', value.value);
2868					}
2869				}
2870
2871				BX.bind(but, 'mousedown', function(e)
2872				{
2873					_this.SelectItem(this.getAttribute('data-bx-dropdown-value'));
2874					_this.editor.CheckCommand(this);
2875					_this.Close();
2876				});
2877
2878				this.arValues[i].listCont = but;
2879			}
2880
2881			if (this.action && this.checkableAction)
2882			{
2883				this.editor.RegisterCheckableAction(this.action, {
2884					action: this.action,
2885					control: this
2886				});
2887			}
2888
2889			BX.bind(this.pCont, 'click', BX.proxy(this.OnClick, this));
2890			BX.bind(this.pCont, "mousedown", BX.delegate(this.OnMouseDown, this));
2891		},
2892
2893		GetCont: function()
2894		{
2895			return this.pCont;
2896		},
2897
2898		GetPopupBindCont: function()
2899		{
2900			return this.pCont;
2901		},
2902
2903		Disable: function(bFlag)
2904		{
2905			if(bFlag != this.disabled)
2906			{
2907				this.disabled = !!bFlag;
2908				if(bFlag)
2909				{
2910					BX.addClass(this.pCont, 'bxhtmled-top-bar-btn-disabled');
2911				}
2912				else
2913				{
2914					BX.removeClass(this.pCont, 'bxhtmled-top-bar-btn-disabled');
2915				}
2916			}
2917		},
2918
2919		OnKeyDown: function(e)
2920		{
2921			if(e.keyCode == 27)
2922			{
2923				this.Close();
2924			}
2925		},
2926
2927		OnClick: function()
2928		{
2929			if(!this.disabled)
2930			{
2931				if (this.bOpened)
2932				{
2933					this.Close();
2934				}
2935				else
2936				{
2937					this.Open();
2938				}
2939			}
2940		},
2941
2942		OnMouseUp: function()
2943		{
2944			this.editor.selection.RestoreBookmark();
2945			if(!this.checked)
2946			{
2947				BX.removeClass(this.pCont, this.activeClassName);
2948			}
2949			BX.unbind(document, 'mouseup', BX.proxy(this.OnMouseUp, this));
2950			BX.removeCustomEvent(this.editor, "OnIframeMouseUp", BX.proxy(this.OnMouseUp, this));
2951		},
2952
2953		OnMouseDown: function()
2954		{
2955			if (!this.disabled)
2956			{
2957				if (this.disabledForTextarea || !this.editor.synchro.IsFocusedOnTextarea())
2958				{
2959					this.savedRange = this.editor.selection.SaveBookmark();
2960				}
2961
2962				BX.addClass(this.pCont, this.activeClassName);
2963				BX.bind(document, 'mouseup', BX.proxy(this.OnMouseUp, this));
2964				BX.addCustomEvent(this.editor, "OnIframeMouseUp", BX.proxy(this.OnMouseUp, this));
2965			}
2966		},
2967
2968		Close: function ()
2969		{
2970			var _this = this;
2971			this.popupShownTimeout = setTimeout(function(){_this.editor.popupShown = false;}, 300);
2972			BX.removeClass(this.pCont, this.activeClassName);
2973			this.pValuesCont.style.display = 'none';
2974			this.editor.overlay.Hide();
2975
2976			BX.unbind(window, "keydown", BX.proxy(this.OnKeyDown, this));
2977			BX.unbind(document, 'mousedown', BX.proxy(this.CheckClose, this));
2978
2979			BX.onCustomEvent(this, "OnPopupClose");
2980
2981			this.bOpened = false;
2982		},
2983
2984		CheckClose: function(e)
2985		{
2986			if (!this.bOpened)
2987			{
2988				return BX.unbind(document, 'mousedown', BX.proxy(this.CheckClose, this));
2989			}
2990
2991			var pEl;
2992			if (e.target)
2993				pEl = e.target;
2994			else if (e.srcElement)
2995				pEl = e.srcElement;
2996			if (pEl.nodeType == 3)
2997				pEl = pEl.parentNode;
2998
2999			if (!BX.findParent(pEl, {className: 'bxhtmled-popup'}))
3000			{
3001				this.Close();
3002			}
3003		},
3004
3005		Open: function ()
3006		{
3007			this.editor.popupShown = true;
3008			if (this.popupShownTimeout)
3009			{
3010				this.popupShownTimeout = clearTimeout(this.popupShownTimeout);
3011			}
3012
3013			var firstOpen = this.pValuesCont.parentNode === null;
3014			document.body.appendChild(this.pValuesCont);
3015
3016			if (firstOpen)
3017			{
3018				BX.ZIndexManager.register(this.pValuesCont);
3019			}
3020
3021			var component = BX.ZIndexManager.bringToFront(this.pValuesCont);
3022			var zIndex = component.getZIndex();
3023
3024			this.pValuesCont.style.display = 'block';
3025			BX.addClass(this.pCont, this.activeClassName);
3026			var
3027				pOverlay = this.editor.overlay.Show({ zIndex: zIndex - 1 }),
3028				bindCont = this.GetPopupBindCont(),
3029				pos = BX.pos(bindCont),
3030				left = Math.round(pos.left - this.pValuesCont.offsetWidth / 2 + bindCont.offsetWidth / 2 + this.posOffset.left),
3031				top = Math.round(pos.bottom + this.posOffset.top),
3032				_this = this;
3033
3034			BX.bind(window, "keydown", BX.proxy(this.OnKeyDown, this));
3035			pOverlay.onclick = function(){_this.Close()};
3036
3037			this.pValuesCont.style.top = top + 'px';
3038			this.pValuesCont.style.left = left + 'px';
3039			this.bOpened = true;
3040
3041			setTimeout(function()
3042			{
3043				BX.bind(document, 'mousedown', BX.proxy(_this.CheckClose, _this));
3044			},100);
3045		},
3046
3047		SelectItem: function(id, val)
3048		{
3049			if (!val)
3050				val = this.arValues[this.valueIndex[id]];
3051
3052			if (this.lastActiveItem)
3053				BX.removeClass(this.lastActiveItem, this.activeListClassName);
3054
3055			if (val)
3056			{
3057				// Select value in list as active
3058				if (val.listCont)
3059				{
3060					this.lastActiveItem = val.listCont;
3061					BX.addClass(val.listCont, this.activeListClassName);
3062				}
3063
3064				this.pCont.className = val.className;
3065				this.pCont.title = BX.util.htmlspecialchars(val.title || val.name || '');
3066			}
3067			else
3068			{
3069				this.pCont.className = this.className;
3070				this.pCont.title = this.title;
3071			}
3072
3073			if (this.disabled)
3074			{
3075				this.disabled = false;
3076				this.Disable(true);
3077			}
3078
3079			return val;
3080		},
3081
3082		SetValue: function()
3083		{
3084		},
3085
3086		GetValue: function()
3087		{
3088		},
3089
3090		OnPopupClose: function()
3091		{
3092		}
3093	};
3094
3095	function DropDownList(editor)
3096	{
3097		// Call parrent constructor
3098		DropDownList.superclass.constructor.apply(this, arguments);
3099		this.className = 'bxhtmled-top-bar-select';
3100		this.itemClassName = 'bxhtmled-dd-list-item';
3101		this.activeListClassName = 'bxhtmled-dd-list-item-active';
3102		this.disabledForTextarea = true;
3103	}
3104	BX.extend(DropDownList, DropDown);
3105
3106	DropDownList.prototype.Create = function ()
3107	{
3108		this.pCont = BX.create("SPAN", {props: {className: this.className, title: this.title}, attrs: {unselectable: 'on'}, text: ''});
3109		if (this.width)
3110			this.pCont.style.width = this.width + 'px';
3111
3112		this.pValuesCont = BX.create("DIV", {props: {className: "bxhtmled-popup bxhtmled-dropdown-list-cont"}, html: '<div class="bxhtmled-popup-corner"></div>'});
3113		this.pValuesContWrap = this.pValuesCont.appendChild(BX.create("DIV", {props: {className: "bxhtmled-dd-list-wrap"}}));
3114		this.valueIndex = {};
3115
3116		if(this.zIndex)
3117		{
3118			this.pValuesCont.style.zIndex = this.zIndex;
3119		}
3120
3121		var but, value, _this = this, itemClass, i, html;
3122		for (i = 0; i < this.arValues.length; i++)
3123		{
3124			value = this.arValues[i];
3125			itemClass = this.itemClassName;
3126			if (value.className)
3127				itemClass += ' ' + value.className;
3128
3129			html = value.tagName ? ('<' + value.tagName + '>' + value.name + '</' + value.tagName + '>') : value.name;
3130			but = this.pValuesContWrap.appendChild(BX.create("SPAN", {props: {title: value.title || value.name, className: itemClass}, html: html, style: value.style}));
3131
3132			but.setAttribute('data-bx-dropdown-value', value.id);
3133			this.valueIndex[value.id] = i;
3134
3135			if (value.defaultValue)
3136				this.SelectItem(null, value);
3137
3138			if (value.action)
3139			{
3140				but.setAttribute('data-bx-type', 'action');
3141				but.setAttribute('data-bx-action', value.action);
3142				if (value.value)
3143					but.setAttribute('data-bx-value', value.value);
3144			}
3145
3146			BX.bind(but, 'mousedown', function(e)
3147			{
3148				if (!e)
3149					e = window.event;
3150				_this.SelectItem(this.getAttribute('data-bx-dropdown-value'));
3151				_this.editor.CheckCommand(this);
3152			});
3153
3154			this.arValues[i].listCont = but;
3155		}
3156
3157		if (this.action && this.checkableAction)
3158		{
3159			this.editor.RegisterCheckableAction(this.action, {
3160				action: this.action,
3161				control: this
3162			});
3163		}
3164
3165		BX.bind(this.pCont, 'click', BX.proxy(this.OnClick, this));
3166	};
3167
3168	DropDownList.prototype.SelectItem = function (valDropdown, val, bClose)
3169	{
3170		bClose = bClose !== false;
3171		if (!val)
3172		{
3173			val = this.arValues[this.valueIndex[valDropdown]];
3174		}
3175
3176		if (this.lastActiveItem)
3177		{
3178			BX.removeClass(this.lastActiveItem, this.activeListClassName);
3179		}
3180
3181		if (val)
3182		{
3183			this.pCont.innerHTML = BX.util.htmlspecialchars((val.topName || val.name || val.id));
3184			this.pCont.title = this.title + ': ' + BX.util.htmlspecialchars(val.title || val.name);
3185
3186
3187			// Select value in list as active
3188			if (val.listCont)
3189			{
3190				this.lastActiveItem = val.listCont;
3191				BX.addClass(val.listCont, this.activeListClassName);
3192			}
3193		}
3194
3195		if (this.bOpened && bClose)
3196		{
3197			this.Close();
3198		}
3199	};
3200
3201	DropDownList.prototype.SetValue = function(active, state)
3202	{
3203	};
3204
3205	DropDownList.prototype.SetWidth = function(width)
3206	{
3207		width = parseInt(width, 10);
3208		if (width)
3209		{
3210			this.width = width;
3211			this.pCont.style.width = width + 'px';
3212		}
3213	};
3214
3215	DropDownList.prototype.Disable = function(bFlag)
3216	{
3217		if(bFlag != this.disabled)
3218		{
3219			this.disabled = !!bFlag;
3220			if(bFlag)
3221			{
3222				BX.addClass(this.pCont, 'bxhtmled-top-bar-select-disabled');
3223			}
3224			else
3225			{
3226				BX.removeClass(this.pCont, 'bxhtmled-top-bar-select-disabled');
3227			}
3228		}
3229	};
3230
3231	// Combobox with multiple choice of values
3232	function ComboBox(editor, params)
3233	{
3234		this.values = [];
3235		this.pInput = params.input;
3236		this.editor = editor;
3237		this.value = params.value || '';
3238		this.defaultValue = params.defaultValue || '';
3239		this.posOffset = {top: 8, left: -4};
3240		this.zIndex = 3010;
3241		this.SPLIT_SYMBOL = ',';
3242		this.itemClassName = 'bxhtmled-dd-list-item';
3243		this.itemClassNameActive = 'bxhtmled-dd-list-item-active';
3244	}
3245
3246	ComboBox.prototype = {
3247		Init: function()
3248		{
3249			BX.bind(this.pInput, 'focus', BX.proxy(this.Focus, this));
3250			BX.bind(this.pInput, 'click', BX.proxy(this.Focus, this));
3251			BX.bind(this.pInput, 'blur', BX.proxy(this.Blur, this));
3252			BX.bind(this.pInput, 'keyup', BX.proxy(this.KeyUp, this));
3253
3254			this.visibleItemsLength = this.values.length;
3255			this.currentItem = false;
3256		},
3257
3258		UpdateValues: function(values)
3259		{
3260			this.bCreated = false;
3261			this.values = values;
3262			this.visibleItemsLength = this.values.length;
3263			this.currentItem = false;
3264			if (this.bOpened)
3265			{
3266				this.ClosePopup();
3267			}
3268		},
3269
3270		Create: function()
3271		{
3272			this.pValuesCont = BX.create("DIV", {props: {className: "bxhtmled-popup bxhtmled-combo-cont"}, html: '<div class="bxhtmled-popup-corner"></div>'});
3273			this.pValuesCont.style.zIndex = this.zIndex;
3274
3275			if (this.pValuesContWrap)
3276			{
3277				BX.cleanNode(this.pValuesContWrap);
3278				this.pValuesCont.appendChild(this.pValuesContWrap);
3279			}
3280			else
3281			{
3282				this.pValuesContWrap = this.pValuesCont.appendChild(BX.create("DIV", {props: {className: "bxhtmled-dd-list-wrap"}}));
3283
3284				BX.bind(this.pValuesContWrap, 'mousedown', function(e)
3285				{
3286					var target = e.target || e.srcElement;
3287					if (!target.getAttribute('data-bx-dropdown-value'))
3288					{
3289						target = BX.findParent(target, function(n)
3290						{
3291							return n.getAttribute && n.getAttribute('data-bx-dropdown-value');
3292						}, _this.pValuesContWrap);
3293					}
3294
3295					if (target)
3296					{
3297						_this.currentItem = parseInt(target.getAttribute('data-bx-dropdown-value'), 10);
3298						_this.SetValueFromList();
3299					}
3300
3301					_this.ClosePopup();
3302				});
3303			}
3304			this.valueIndex = {};
3305
3306			var but, value, _this = this, itemClass, i, html;
3307			for (i = 0; i < this.values.length; i++)
3308			{
3309				value = this.values[i];
3310				itemClass = this.itemClassName || '';
3311				this.values[i].TITLE = this.values[i].TITLE || this.values[i].NAME;
3312
3313				if (this.values[i].VALUE && this.values[i].VALUE !== this.values[i].TITLE)
3314				{
3315					this.values[i].TITLE += ' (' + this.values[i].VALUE + ')';
3316				}
3317				else
3318				{
3319					this.values[i].VALUE = this.values[i].NAME;
3320				}
3321
3322				but = this.pValuesContWrap.appendChild(BX.create("SPAN", {props: {className: itemClass}, html: value.TITLE}));
3323				but.setAttribute('data-bx-dropdown-value', i);
3324				this.values[i].cont = but;
3325			}
3326
3327			this.bCreated = true;
3328		},
3329
3330		KeyUp: function(e)
3331		{
3332			var keyCode = e.keyCode;
3333			if (keyCode == this.editor.KEY_CODES['down'])
3334			{
3335				this.SelectItem(1);
3336			}
3337			else if (keyCode == this.editor.KEY_CODES['up'])
3338			{
3339				this.SelectItem(-1);
3340			}
3341			else if (keyCode == this.editor.KEY_CODES['escape'])
3342			{
3343				if (this.bOpened)
3344				{
3345					this.ClosePopup();
3346					return BX.PreventDefault(e);
3347				}
3348			}
3349			else if (keyCode == this.editor.KEY_CODES['enter'])
3350			{
3351				if (this.bOpened)
3352				{
3353					this.SetValueFromList();
3354					this.ClosePopup();
3355					return BX.PreventDefault(e);
3356				}
3357			}
3358			else
3359			{
3360				this.FilterValue();
3361			}
3362		},
3363
3364		FilterValue: function()
3365		{
3366			// Range
3367			var
3368				i, val,
3369				splitedVals = this.GetSplitedValues(),
3370				caretPos = this.GetCaretPos(this.pInput);
3371
3372			for (i = 0; i < splitedVals.length; i++)
3373			{
3374				val = splitedVals[i];
3375				if (caretPos >= val.start && caretPos <= val.end)
3376				{
3377					break;
3378				}
3379			}
3380
3381			// Filter values && highlight values
3382			this.FilterAndHighlight(val.value);
3383		},
3384
3385		GetSplitedValues: function()
3386		{
3387			var
3388				arVals, i, gStart, gEnd, val,
3389				res = [],
3390				str = this.pInput.value;
3391
3392			if (str.indexOf(this.SPLIT_SYMBOL) === -1 || this.bMultiple === false)
3393			{
3394				res.push(
3395					{
3396						start: 0,
3397						end: str.length,
3398						value: BX.util.trim(str)
3399					}
3400				);
3401			}
3402			else
3403			{
3404				arVals = str.split(this.SPLIT_SYMBOL);
3405				gStart = 0;
3406				gEnd = 0;
3407				for (i = 0; i < arVals.length; i++)
3408				{
3409					val = arVals[i];
3410					gEnd += val.length + i;
3411					res.push(
3412						{
3413							start: gStart,
3414							end: gEnd,
3415							value: BX.util.trim(val)
3416						}
3417					);
3418					gStart = gEnd;
3419				}
3420			}
3421
3422			return res;
3423		},
3424
3425		FilterAndHighlight: function(needle)
3426		{
3427			needle = BX.util.trim(needle);
3428			var val, i, showPopup = false, pos;
3429
3430			this.visibleItemsLength = 0;
3431			for (i = 0; i < this.values.length; i++)
3432			{
3433				val = this.values[i];
3434				if (needle === '')
3435				{
3436					showPopup = true;
3437					val.cont.style.display = '';
3438					this.visibleItemsLength++;
3439				}
3440				else
3441				{
3442					pos = val.TITLE.toLowerCase().indexOf(needle.toLowerCase());
3443					if (pos !== -1 || needle == '')
3444					{
3445						val.cont.innerHTML = BX.util.htmlspecialchars(val.TITLE.substr(0, pos)) + '<b>' + BX.util.htmlspecialchars(needle) + '</b>' + BX.util.htmlspecialchars(val.TITLE.substr(pos + needle.length));
3446						showPopup = true;
3447						val.cont.style.display = '';
3448						val.cont.setAttribute('data-bx-dropdown-value', this.visibleItemsLength);
3449						this.visibleItemsLength++;
3450					}
3451					else
3452					{
3453						val.cont.innerHTML = BX.util.htmlspecialchars(val.TITLE);
3454						val.cont.style.display = 'none';
3455					}
3456				}
3457			}
3458
3459			this.currentItem = false;
3460
3461			if (showPopup && !this.bOpened)
3462			{
3463				this.ShowPopup();
3464			}
3465			else if (!showPopup && this.bOpened)
3466			{
3467				this.ClosePopup();
3468			}
3469		},
3470
3471		GetCaretPos: function(input)
3472		{
3473			var caretPos = 0;
3474
3475			// IE Support
3476			if (document.selection)
3477			{
3478				BX.focus(input);
3479				var oSel = document.selection.createRange();
3480				oSel.moveStart ('character', - input.value.length);
3481				// The caret position is selection length
3482				caretPos = oSel.text.length;
3483			}
3484			else if (input.selectionStart || input.selectionStart == '0')
3485			{
3486				caretPos = input.selectionStart;
3487			}
3488			return (caretPos);
3489		},
3490
3491		SetValue: function(value)
3492		{
3493			this.pInput.value = value;
3494		},
3495
3496		SetValueFromList: function()
3497		{
3498			var ind = 0, val, i;
3499			for (i = 0; i < this.values.length; i++)
3500			{
3501				val = this.values[i];
3502				if (val.cont.style.display != 'none')
3503				{
3504					if (ind == this.currentItem)
3505					{
3506						BX.addClass(val.cont, this.itemClassNameActive);
3507						break;
3508					}
3509					ind++;
3510				}
3511			}
3512
3513			var
3514				splVal,
3515				splitedVals = this.GetSplitedValues(),
3516				caretPos = this.GetCaretPos(this.pInput);
3517
3518			for (i = 0; i < splitedVals.length; i++)
3519			{
3520				splVal = splitedVals[i];
3521				if (caretPos >= splVal.start && caretPos <= splVal.end)
3522				{
3523					break;
3524				}
3525			}
3526
3527			var
3528				glue = this.SPLIT_SYMBOL == ' ' ? ' ' : this.SPLIT_SYMBOL + ' ',
3529				curValue = this.pInput.value,
3530				before = curValue.substr(0, splVal.start),
3531				after = curValue.substr(splVal.end);
3532
3533			before = before.replace(/^[\s\r\n\,]+/g, '').replace(/[\s\r\n\,]+$/g, '');
3534			after = after.replace(/^[\s\r\n\,]+/g, '').replace(/[\s\r\n\,]+$/g, '');
3535
3536			this.pInput.value = before +
3537				(before == '' ? '' : glue) +
3538				val.VALUE +
3539				(after == '' ? '' : glue) +
3540				after;
3541
3542			this.FilterAndHighlight('');
3543		},
3544
3545		SelectItem: function(delta)
3546		{
3547			var ind, val, i, len;
3548			if (this.currentItem === false)
3549			{
3550				this.currentItem = 0;
3551			}
3552			else if(delta !== undefined)
3553			{
3554				this.currentItem += delta;
3555
3556				if (this.currentItem > this.visibleItemsLength - 1)
3557				{
3558					this.currentItem = 0;
3559				}
3560				else if(this.currentItem < 0)
3561				{
3562					this.currentItem = this.visibleItemsLength - 1;
3563				}
3564			}
3565
3566			if (document.querySelectorAll)
3567			{
3568				var selected = this.pValuesContWrap.querySelectorAll("." + this.itemClassNameActive);
3569				if (selected)
3570				{
3571					for (i = 0; i < selected.length; i++)
3572					{
3573						BX.removeClass(selected[i], this.itemClassNameActive);
3574					}
3575				}
3576			}
3577
3578			ind = 0;
3579			len = this.values.length;
3580			for (i = 0; i < this.values.length; i++)
3581			{
3582				val = this.values[i];
3583				if (val.cont.style.display != 'none')
3584				{
3585					if (ind == this.currentItem)
3586					{
3587						BX.addClass(val.cont, this.itemClassNameActive);
3588						break;
3589					}
3590					ind++;
3591				}
3592			}
3593		},
3594
3595		Focus: function(e)
3596		{
3597			if (this.values.length > 0 && !this.bFocused)
3598			{
3599				BX.focus(this.pInput);
3600				this.bFocused = true;
3601				if (this.value == this.defaultValue)
3602				{
3603					this.value = '';
3604				}
3605
3606				this.ShowPopup();
3607			}
3608		},
3609
3610		Blur: function()
3611		{
3612			if (this.values.length > 0 && this.bFocused)
3613			{
3614				this.bFocused = false;
3615				this.ClosePopup();
3616			}
3617		},
3618
3619		ShowPopup: function()
3620		{
3621			if (!this.bCreated)
3622			{
3623				this.Create();
3624			}
3625
3626			this.editor.popupShown = true;
3627			if (this.popupShownTimeout)
3628			{
3629				this.popupShownTimeout = clearTimeout(this.popupShownTimeout);
3630			}
3631
3632			var firstShow = this.pValuesCont.parentNode === null;
3633			document.body.appendChild(this.pValuesCont);
3634
3635			if (firstShow)
3636			{
3637				BX.ZIndexManager.register(this.pValuesCont);
3638			}
3639
3640			BX.ZIndexManager.bringToFront(this.pValuesCont);
3641
3642			this.pValuesCont.style.display = 'block';
3643
3644			var
3645				i,
3646				pos = BX.pos(this.pInput),
3647				left = pos.left + this.posOffset.left,
3648				top = pos.bottom + this.posOffset.top;
3649
3650			this.pValuesCont.style.top = top + 'px';
3651			this.pValuesCont.style.left = left + 'px';
3652			this.bOpened = true;
3653
3654			if (document.querySelectorAll)
3655			{
3656				var selected = this.pValuesContWrap.querySelectorAll("." + this.itemClassNameActive);
3657				if (selected)
3658				{
3659					for (i = 0; i < selected.length; i++)
3660					{
3661						BX.removeClass(selected[i], this.itemClassNameActive);
3662					}
3663				}
3664			}
3665
3666			BX.onCustomEvent(this, "OnComboPopupOpen");
3667		},
3668
3669		ClosePopup: function()
3670		{
3671			var _this = this;
3672			this.popupShownTimeout = setTimeout(function(){_this.editor.popupShown = false;}, 300);
3673			this.pValuesCont.style.display = 'none';
3674			this.editor.overlay.Hide();
3675
3676			this.bOpened = false;
3677			BX.onCustomEvent(this, "OnComboPopupClose");
3678		},
3679
3680		OnChange: function()
3681		{
3682		},
3683
3684		CheckClose: function(e)
3685		{
3686			if (!this.bOpened)
3687			{
3688				return BX.unbind(document, 'mousedown', BX.proxy(this.CheckClose, this));
3689			}
3690
3691			var pEl;
3692			if (e.target)
3693				pEl = e.target;
3694			else if (e.srcElement)
3695				pEl = e.srcElement;
3696			if (pEl.nodeType == 3)
3697				pEl = pEl.parentNode;
3698
3699			if (!BX.findParent(pEl, {className: 'bxhtmled-popup'}))
3700				this.Close();
3701		}
3702	};
3703
3704	function ClassSelector(editor, params)
3705	{
3706		// Call parrent constructor
3707		ClassSelector.superclass.constructor.apply(this, arguments);
3708		this.filterTag = params.filterTag || '';
3709		this.lastTemplateId = this.editor.GetTemplateId();
3710		this.values = this.GetClasses();
3711		this.SPLIT_SYMBOL = ' ';
3712		this.Init();
3713	}
3714	BX.extend(ClassSelector, ComboBox);
3715
3716	ClassSelector.prototype.OnChange = function()
3717	{
3718		if (this.lastTemplateId != this.editor.GetTemplateId())
3719		{
3720			this.lastTemplateId = this.editor.GetTemplateId();
3721			this.values = this.GetClasses();
3722			this.bCreated = false;
3723		}
3724	};
3725
3726	ClassSelector.prototype.GetClasses = function()
3727	{
3728		var
3729			title,
3730			classes = this.editor.GetCurrentCssClasses(this.filterTag);
3731
3732		this.values = [];
3733		if (classes && classes.length > 0)
3734		{
3735			for (var i = 0; i < classes.length; i++)
3736			{
3737				title = null;
3738				if (classes[i].classTitle && typeof classes[i].classTitle == 'object')
3739				{
3740					if (classes[i].classTitle.title)
3741					{
3742						title = classes[i].classTitle.title;
3743					}
3744					else
3745					{
3746						continue;
3747					}
3748				}
3749				else if (classes[i].classTitle)
3750				{
3751					title = classes[i].classTitle;
3752				}
3753
3754				this.values.push(
3755					{
3756						VALUE: classes[i].className,
3757						TITLE:  title,
3758						NAME: classes[i].className
3759					}
3760				);
3761			}
3762		}
3763		return this.values;
3764	};
3765
3766	function PasteControl(editor)
3767	{
3768		this.editor = editor;
3769		if (this.editor.config.pasteSetColors !== this.editor.config.pasteSetBorders ||
3770				this.editor.config.pasteSetColors !== this.editor.config.pasteSetDecor)
3771		{
3772			this.mode = 'default';
3773		}
3774		else
3775		{
3776			this.mode = this.editor.config.pasteSetColors &&
3777			this.editor.config.pasteSetBorders &&
3778			this.editor.config.pasteSetDecor ? 'text' : 'rich';
3779		}
3780
3781		var _this = this;
3782
3783		this.items = [];
3784		if (this.mode == 'default')
3785		{
3786			this.items = [
3787				{
3788					TEXT: BX.message('BXEdPasteDefault'),
3789					ACTION: function()
3790					{
3791						_this.Hide('default');
3792					},
3793					CHECKED: this.mode == 'default',
3794					_MODE: 'default'
3795				}
3796			];
3797		}
3798
3799		this.items.push({
3800			TEXT: BX.message('BXEdPasteText'),
3801			ACTION: function()
3802			{
3803				_this.Hide('text');
3804			},
3805			CHECKED: this.mode == 'text',
3806			_MODE: 'text'
3807		});
3808		this.items.push({
3809			TEXT: BX.message('BXEdPasteFormattedText'),
3810			ACTION: function()
3811			{
3812				_this.Hide('rich');
3813			},
3814			CHECKED: this.mode == 'rich',
3815			_MODE: 'rich'
3816		});
3817
3818		// mantis: 71968
3819		BX.addCustomEvent('OnComponentParamsDisplay', BX.proxy(this.Hide, this));
3820	}
3821
3822	PasteControl.prototype = {
3823		CheckAndShow: function ()
3824		{
3825			var
3826				isOpened = this.isOpened,
3827				_this = this;
3828
3829			this.savedRange = this.editor.selection.GetBookmark();
3830			this.isOpened = true;
3831			this.lastPreviewMode = false;
3832			if (this.checkTimeout)
3833				this.checkTimeout = clearTimeout(this.checkTimeout);
3834
3835			this.checkTimeout = setTimeout(function()
3836			{
3837				var skipPasteHandler = _this.editor.skipPasteHandler;
3838				_this.editor.skipPasteHandler = true;
3839				_this.PreviewContent({mode: 'rich', doTimeout: false, skipColors: true});
3840				var richContent = _this.editor.iframeView.GetValue();
3841				// Clear images before comparision
3842				richContent = richContent.replace(/<img((?:\s|\S)*?)>/ig, '');
3843				richContent = richContent.replace(/id="(\s|\S)*?"/ig, '');
3844				richContent = richContent.replace(/\s+/ig, ' ');
3845
3846				_this.PreviewContent({mode: 'text', doTimeout: false});
3847				var textContent = _this.editor.iframeView.GetValue();
3848				// Clear images before comparision
3849				textContent = textContent.replace(/<img((?:\s|\S)*?)>/ig, '');
3850				textContent = textContent.replace(/id="(\s|\S)*?"/ig, '');
3851				textContent = textContent.replace(/\s+/ig, ' ');
3852
3853				if (richContent != textContent)
3854				{
3855					_this.isOpened = isOpened;
3856					_this.editor.SetCursorNode(_this.savedRange);
3857					_this.Show();
3858				}
3859
3860				_this.editor.skipPasteHandler = skipPasteHandler;
3861			}, 200);
3862		},
3863
3864		Show: function ()
3865		{
3866			var _this = this;
3867			this.lastPreviewMode = false;
3868			this.Hide();
3869
3870			this.pOverlay = this.editor.overlay.Show({zIndex: 1040});
3871			BX.bind(this.pOverlay, 'click', BX.proxy(this.Hide, this));
3872			BX.bind(this.pOverlay, 'mousemove', BX.proxy(function(){this.PreviewContent({mode: 'default'});}, this));
3873
3874			if (!this.dummyTarget)
3875			{
3876				this.dummyTarget = this.editor.dom.iframeCont.appendChild(BX.create('DIV', {props: {className: 'bxhtmled-dummy-target'}}));
3877			}
3878
3879			var
3880				cursorNode = this.editor.GetIframeElement('bx-cursor-node'),
3881				top = 0, left = 0, node;
3882
3883			if (cursorNode)
3884			{
3885				if (cursorNode.parentNode)
3886				{
3887					top += cursorNode.offsetHeight;
3888					node = cursorNode;
3889					do
3890					{
3891						top += node.offsetTop || 0;
3892						left += node.offsetLeft || 0;
3893						node = node.offsetParent;
3894					} while (node && node.nodeName != 'BODY');
3895				}
3896			}
3897
3898			var scrollPos = BX.GetWindowScrollPos(this.editor.GetIframeDoc());
3899
3900			top -= scrollPos.scrollTop;
3901			left -= scrollPos.scrollLeft;
3902
3903			var
3904				editorSize = this.editor.GetSceletonSize(),
3905				maxTop = editorSize.height - this.items.length * 40,
3906				maxLeft = editorSize.width - 100;
3907
3908			if (top < 0)
3909				top = 0;
3910			else if (top > maxTop)
3911				top = maxTop;
3912
3913			if (left < 0)
3914				left = 0;
3915			else if (left > maxLeft)
3916				left = maxLeft;
3917
3918			this.dummyTarget.style.left = left + 'px';
3919			this.dummyTarget.style.top = top + 'px';
3920			this.dummyTarget.style.zIndex = '2002';
3921
3922			this.OPENER = new BX.COpener({
3923				DIV: this.dummyTarget, MENU: this.items, TYPE: 'click', ACTIVE_CLASS: 'adm-btn-active', CLOSE_ON_CLICK: true
3924			});
3925			this.OPENER.Open();
3926
3927			var popupDiv = this.OPENER.GetMenu().DIV;
3928			BX.addClass(popupDiv, 'bxhtmled-paste-control bx-core-popup-menu-editor');
3929
3930			BX.addCustomEvent(this.OPENER.GetMenu(), 'onMenuClose', BX.proxy(this.Hide, this));
3931
3932			var i, items = BX.findChild(popupDiv, {className: 'bx-core-popup-menu-item'}, 1, 1);
3933			for (i = 0; i < items.length; i++)
3934			{
3935				items[i].setAttribute('data-bx-mode', this.items[i]._MODE);
3936			}
3937
3938			BX.bind(popupDiv, 'mousemove', function(e)
3939			{
3940				var
3941					target = e.target || e.srcElement,
3942					mode = (target && target.getAttribute) ? target.getAttribute('data-bx-mode') : null;
3943
3944				if (!mode)
3945				{
3946					target = BX.findParent(target, function(n)
3947					{
3948						return n == popupDiv || (n.getAttribute && n.getAttribute('data-bx-mode'));
3949					}, popupDiv);
3950					mode = (target && target.getAttribute) ? target.getAttribute('data-bx-mode') : null;
3951				}
3952
3953				_this.PreviewContent({mode: mode});
3954			});
3955
3956			this.isOpened = true;
3957			BX.addCustomEvent(this.editor, 'OnIframeKeydown', BX.CMenu.broadcastCloseEvent);
3958			BX.bind(document.body, "keydown", BX.CMenu.broadcastCloseEvent);
3959		},
3960
3961		Hide: function (mode)
3962		{
3963			if (this.isOpened)
3964			{
3965				this.editor.overlay.Hide();
3966				if (this.pOverlay)
3967				{
3968					BX.unbind(this.pOverlay, 'click', BX.proxy(this.Hide, this));
3969					BX.unbind(this.pOverlay, 'mousemove', BX.proxy(this.PreviewContent, this));
3970				}
3971
3972				BX.removeCustomEvent(this.editor, 'OnIframeKeydown', BX.CMenu.broadcastCloseEvent);
3973				BX.unbind(document.body, "keydown", BX.CMenu.broadcastCloseEvent);
3974
3975				if (!mode || typeof mode !== 'string' || (mode !== 'text' && mode !== 'rich'))
3976					mode = 'default';
3977				this.PreviewContent({mode: mode});
3978				this.editor.Focus();
3979
3980				this.editor.On("OnIframePaste");
3981				this.editor.On("OnIframeNewWord");
3982
3983				this.isOpened = false;
3984			}
3985		},
3986
3987		PreviewContent: function(params)
3988		{
3989			if (this.isOpened)
3990			{
3991				if (this.lastPreviewMode != params.mode || !this.lastPreviewMode)
3992				{
3993					if (params.doTimeout !== false)
3994					{
3995						params.doTimeout = false;
3996						var _this = this;
3997						if (this.previewTimeout)
3998							clearTimeout(this.previewTimeout);
3999						this.previewTimeout = setTimeout(function(){_this.PreviewContent(params);}, 200);
4000						return;
4001					}
4002
4003					if (params.mode == 'rich')
4004					{
4005						this.editor.config.pasteSetColors = !!params.skipColors;
4006						this.editor.config.pasteSetBorders = false;
4007						this.editor.config.pasteSetDecor = false;
4008					}
4009					else if (params.mode == 'text')
4010					{
4011						this.editor.config.pasteSetColors = true;
4012						this.editor.config.pasteSetBorders = true;
4013						this.editor.config.pasteSetDecor = true;
4014					}
4015					else // rich
4016					{
4017						this.editor.config.pasteSetColors = this.defPasteSetColors;
4018						this.editor.config.pasteSetBorders = this.defPasteSetBorders;
4019						this.editor.config.pasteSetDecor = this.defPasteSetDecor;
4020					}
4021
4022					this.editor.pasteHandleMode = true;
4023					this.editor.bbParseContentMode = true;
4024					this.editor.synchro.lastIframeValue = false;
4025
4026					this.editor.iframeView.SetValue(this.pastedContent, false);
4027					this.editor.synchro.FromIframeToTextarea(true, true);
4028
4029					this.editor.pasteHandleMode = false;
4030					this.editor.bbParseContentMode = false;
4031
4032					this.editor.synchro.lastTextareaValue = false;
4033					this.editor.synchro.FromTextareaToIframe(true);
4034
4035					this.editor.RestoreCursor();
4036
4037					this.editor.skipPasteHandler = false;
4038
4039					// Restore settings
4040					if (params.mode != 'default')
4041					{
4042						this.editor.config.pasteSetColors = this.defPasteSetColors;
4043						this.editor.config.pasteSetBorders = this.defPasteSetBorders;
4044						this.editor.config.pasteSetDecor = this.defPasteSetDecor;
4045					}
4046				}
4047				this.lastPreviewMode = params.mode;
4048			}
4049		},
4050
4051		SaveIframeContent: function(content)
4052		{
4053			// Save default values
4054			this.defPasteSetColors = this.editor.config.pasteSetColors;
4055			this.defPasteSetBorders = this.editor.config.pasteSetBorders;
4056			this.defPasteSetDecor = this.editor.config.pasteSetDecor;
4057
4058			this.pastedContent = content;
4059		}
4060	};
4061
4062	function __run()
4063	{
4064		window.BXHtmlEditor.TaskbarManager = TaskbarManager;
4065		window.BXHtmlEditor.Taskbar = Taskbar;
4066		window.BXHtmlEditor.ComponentsControl = ComponentsControl;
4067		window.BXHtmlEditor.ContextMenu = ContextMenu;
4068		window.BXHtmlEditor.Dialog = Dialog;
4069		window.BXHtmlEditor.Toolbar = Toolbar;
4070		window.BXHtmlEditor.NodeNavigator = NodeNavi;
4071		window.BXHtmlEditor.Button = Button;
4072		window.BXHtmlEditor.DropDown = DropDown;
4073		window.BXHtmlEditor.DropDownList = DropDownList;
4074		window.BXHtmlEditor.ComboBox = ComboBox;
4075		window.BXHtmlEditor.ClassSelector = ClassSelector;
4076		window.BXHtmlEditor.Overlay = Overlay;
4077		window.BXHtmlEditor.PasteControl = PasteControl;
4078
4079		BX.onCustomEvent(window.BXHtmlEditor, 'OnEditorBaseControlsDefined');
4080	}
4081
4082	if (window.BXHtmlEditor)
4083	{
4084		__run();
4085	}
4086	else
4087	{
4088		BX.addCustomEvent(window, "OnBXHtmlEditorInit", __run);
4089	}
4090})();

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.