PageSourceSearch

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

js voenchast.ru collected 2026-10-09 22:40:28 UTC 124,945 bytes, 5,183 lines download raw bytes

1/**
2 * Bitrix HTML Editor 3.0
3 * Date: 24.04.13
4 * Time: 4:23
5 *
6 * Commands class
7 * Rich Text Query/Formatting Commands
8 */
9(function()
10{
11	function BXEditorActions(editor)
12	{
13		this.editor = editor;
14		this.document = editor.sandbox.GetDocument();
15		BX.addCustomEvent(this.editor, 'OnIframeReInit', BX.proxy(function(){this.document = this.editor.sandbox.GetDocument();}, this));
16		this.actions = this.GetActionList();
17		this.contentActionIndex = {
18			removeFormat: 1,
19			bold: 1,
20			italic: 1,
21			underline: 1,
22			strikeout: 1,
23			fontSize: 1,
24			foreColor: 1,
25			backgroundColor: 1,
26			formatInline: 1,
27			formatBlock: 1,
28			createLink: 1,
29			insertHTML: 1,
30			insertImage: 1,
31			insertLineBreak: 1,
32			removeLink: 1,
33			insertOrderedList: 1,
34			insertUnorderedList: 1,
35			align: 1,
36			indent: 1,
37			outdent: 1,
38			formatStyle: 1,
39			fontFamily: 1,
40			universalFormatStyle: 1,
41			quote: 1,
42			code: 1,
43			sub: 1,
44			sup: 1,
45			insertSmile: 1
46		};
47	}
48
49	BXEditorActions.prototype =
50	{
51		IsSupportedByBrowser: function(action)
52		{
53			// Following actions are supported but contain bugs in some browsers
54			var
55				isIe = BX.browser.IsIE() || BX.browser.IsIE10() || BX.browser.IsIE11(),
56				arBuggyActions = {
57					indent: isIe,
58					outdent: isIe,
59					formatBlock: isIe,
60					insertUnorderedList: BX.browser.IsIE() || BX.browser.IsOpera(),
61					insertOrderedList: BX.browser.IsIE() || BX.browser.IsOpera()
62				},
63				arSupportedActions = { // Firefox throws some errors for queryCommandSupported...
64					insertHTML: BX.browser.IsFirefox()
65				};
66
67			if (!arBuggyActions[action])
68			{
69				// Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
70				try {
71					return this.document.queryCommandSupported(action);
72				} catch(e1) {}
73
74				try {
75					return this.document.queryCommandEnabled(action);
76				} catch(e2) {
77					return !!arSupportedActions[action];
78				}
79			}
80			return false;
81		},
82
83		IsSupported: function(action)
84		{
85			return !!this.actions[action];
86		},
87
88		IsContentAction: function(action)
89		{
90			return this.contentActionIndex[action];
91		},
92
93		Exec: function(action, value, bSilent)
94		{
95			var
96				_this = this,
97				oAction = this.actions[action],
98				func = oAction && oAction.exec,
99				isContentAction = this.IsContentAction(action),
100				result = null;
101
102			if (!bSilent)
103			{
104				this.editor.On("OnBeforeCommandExec", [isContentAction, action, oAction, value]);
105			}
106
107			if (isContentAction)
108			{
109				this.editor.Focus(false);
110			}
111
112			if (func)
113			{
114				result = func.apply(oAction, arguments);
115			}
116			else
117			{
118				try
119				{
120					result = this.document.execCommand(action, false, value);
121				} catch(e){}
122			}
123
124			if (isContentAction)
125			{
126				setTimeout(function(){_this.editor.Focus(false);}, 1);
127			}
128
129			if (!bSilent)
130			{
131				this.editor.On("OnAfterCommandExec", [isContentAction, action]);
132			}
133
134			return result;
135		},
136
137		CheckState: function(action, value)
138		{
139			var
140				oAction = this.actions[action],
141				result = null;
142
143			if (oAction && oAction.state)
144			{
145				result = oAction.state.apply(oAction, arguments);
146			}
147			else
148			{
149				try
150				{
151					result = this.document.queryCommandState(action);
152				}
153				catch(e)
154				{
155					result = false;
156				}
157			}
158			return result;
159		},
160
161		/**
162		 * Get the current command's value
163		 *
164		 * @param {String} command The command string which to check (eg. "formatBlock")
165		 * @return {String} The command value
166		 * @example
167		 *    var currentBlockElement = commands.value("formatBlock");
168		 */
169		GetValue: function(command)
170		{
171			var
172				obj = this.commands[command],
173				method  = obj && obj.value;
174
175			if (method)
176			{
177				return method.call(obj, this.composer, command);
178			}
179			else
180			{
181				try {
182					// try/catch for buggy firefox
183					return this.document.queryCommandValue(command);
184				} catch(e) {
185					return null;
186				}
187			}
188		},
189
190		GetActionList: function()
191		{
192			this.actions = {
193				changeView: this.GetChangeView(),
194				splitMode: this.GetChangeSplitMode(),
195				fullscreen: this.GetFullscreen(),
196				changeTemplate: this.GetChangeTemplate(),
197				removeFormat: this.GetRemoveFormat(),
198
199				// Format text
200				bold: this.GetBold(),
201				italic: this.GetItalic(),
202				underline: this.GetUnderline(),
203				strikeout: this.GetStrikeout(),
204				// Size
205				fontSize: this.GetFontSize(),
206				// Color
207				foreColor: this.GetForeColor(),
208				backgroundColor: this.GetBackgroundColor(),
209				//
210				formatInline: this.GetFormatInline(),
211				formatBlock: this.GetFormatBlock(),
212				// Insert
213				createLink: this.GetCreateLink(),
214				insertHTML: this.GetInsertHTML(),
215				insertImage: this.GetInsertImage(),
216				insertLineBreak: this.GetInsertLineBreak(),
217				insertTable: this.GetInsertTable(),
218				removeLink: this.GetRemoveLink(),
219				insertHr: this.GetInsertHr(),
220				// Lists
221				insertOrderedList: this.GetInsertList({bOrdered: true}),
222				insertUnorderedList: this.GetInsertList({bOrdered: false}),
223				align: this.GetAlign(),
224				indent: this.GetIndent(),
225				outdent: this.GetOutdent(),
226
227				formatStyle: this.GetFormatStyle(),
228				fontFamily: this.GetFontFamily(),
229				universalFormatStyle: this.GetUniversalFormatStyle(),
230
231				selectNode: this.GetSelectNode(),
232				doUndo: this.GetUndoRedo(true),
233				doRedo: this.GetUndoRedo(false),
234
235				sub: this.GetSubSup('sub'),
236				sup: this.GetSubSup('sup'),
237				quote: this.GetQuote(),
238				code: this.GetCode(),
239				insertSmile: this.GetInsertSmile(),
240				tableOperation: this.GetTableOperation(),
241
242				// Bbcodes actions
243				formatBbCode: this.GetFormatBbCode()
244			};
245
246			this.editor.On("OnGetActionsList");
247
248			return this.actions;
249		},
250
251		GetChangeView: function()
252		{
253			var _this = this;
254			return {
255				exec: function()
256				{
257					var value = arguments[1];
258					if ({'code': 1, 'wysiwyg': 1, 'split': 1}[value])
259						_this.editor.SetView(value)
260				},
261
262				state: function() {
263					return false;
264				},
265
266				value: function() {}
267			};
268		},
269
270		GetChangeSplitMode: function()
271		{
272			var _this = this;
273			return {
274				exec: function()
275				{
276					_this.editor.SetSplitMode(arguments[1] == 1);
277				},
278
279				state: function() {
280					return _this.editor.GetSplitMode();
281				},
282
283				value: function() {}
284			};
285		},
286
287		GetFullscreen: function()
288		{
289			var _this = this;
290			return {
291				exec: function()
292				{
293					_this.editor.Expand();
294				},
295
296				state: function()
297				{
298					return _this.editor.IsExpanded();
299				},
300
301				value: function() {}
302			};
303		},
304
305		/**
306		 * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
307		 *
308		 *   #1 caret in unformatted text:
309		 *      abcdefg|
310		 *   output:
311		 *      abcdefg<b>|</b>
312		 *
313		 *   #2 unformatted text selected:
314		 *      abc|deg|h
315		 *   output:
316		 *      abc<b>|deg|</b>h
317		 *
318		 *   #3 unformatted text selected across boundaries:
319		 *      ab|c <span>defg|h</span>
320		 *   output:
321		 *      ab<b>|c </b><span><b>defg</b>|h</span>
322		 *
323		 *   #4 formatted text entirely selected
324		 *      <b>|abc|</b>
325		 *   output:
326		 *      |abc|
327		 *
328		 *   #5 formatted text partially selected
329		 *      <b>ab|c|</b>
330		 *   output:
331		 *      <b>ab</b>|c|
332		 *
333		 *   #6 formatted text selected across boundaries
334		 *      <span>ab|c</span> <b>de|fgh</b>
335		 *   output:
336		 *      <span>ab|c</span> de|<b>fgh</b>
337		 */
338		GetFormatInline: function()
339		{
340			var
341				_this = this,
342				TAG_ALIAS = {
343					strong: "b",
344					em: "i",
345					b: "strong",
346					i: "em"
347				},
348				htmlApplier = {};
349
350			function getKey(tagName, style, className)
351			{
352				var key = tagName + ":";
353				if (className)
354					key += className;
355				if (style)
356				{
357					for (var i in style)
358						if (style.hasOwnProperty(i))
359							key += i + '=' + style[i] + ';';
360				}
361				return key;
362			}
363
364			function getStyler(tagName, style, className)
365			{
366				var key = getKey(tagName, style, className);
367				if (!htmlApplier[key])
368				{
369					var alias = TAG_ALIAS[tagName];
370					var tags = alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
371					htmlApplier[key] = new _this.editor.HTMLStyler(_this.editor, tags, style, className, true);
372				}
373				return htmlApplier[key];
374			}
375
376			return {
377				exec: function(command, value, tagName, arStyle, cssClass, params)
378				{
379					params = (!params || typeof params != 'object') ? {} : params;
380					_this.editor.iframeView.Focus();
381					var range = _this.editor.selection.GetRange();
382					if (!range)
383					{
384						return false;
385					}
386
387					var applier = getStyler(tagName, arStyle, cssClass);
388
389					if (params.bClear)
390					{
391						range = applier.UndoToRange(range, false);
392					}
393					else
394					{
395						range = applier.ToggleRange(range);
396					}
397
398					setTimeout(function()
399					{
400						_this.editor.selection.SetSelection(range);
401
402						// If we format text in the table - we could accidentally broke table,
403						// so now we should check and repair all possible tables (mantis: #66342)
404						var
405							table, i, j, k, cellNode,
406							tables = [],
407							bogusNodes = [],
408							nodes = range.getNodes([1]);
409
410						for (i = 0; i < nodes.length; i++)
411						{
412							if (BX.util.in_array(nodes[i].nodeName, ['TD', 'TR', 'TH']))
413							{
414								table = BX.findParent(nodes[i], function(n)
415								{
416									return n.nodeName == "TABLE";
417								}, _this.editor.GetIframeDoc().body);
418
419								if (table && !BX.util.in_array(table, tables))
420								{
421									tables.push(table);
422								}
423							}
424						}
425
426						// Now we have list of tables. In most cases it will be one node.
427						for (i = 0; i < tables.length; i++)
428						{
429							table = tables[i];
430							for (j = 0; j < table.rows.length; j++)
431							{
432								for (k = 0; k < table.rows[j].childNodes.length; k++)
433								{
434									cellNode = table.rows[j].childNodes[k];
435									// If it's cell inside the row, it should at least be on of the table dedicated tag. If not - we will remove it to save html valid.
436									if (cellNode
437										&& cellNode.nodeType == 1
438										&& !BX.util.in_array(cellNode.nodeName, _this.editor.TABLE_TAGS))
439									{
440										bogusNodes.push(cellNode);
441									}
442								}
443							}
444						}
445
446						// Clean invalid tags inside the table.
447						for (i = 0; i < bogusNodes.length; i++)
448						{
449							BX.cleanNode(bogusNodes[i], true);
450						}
451
452						var lastCreatedNode = _this.editor.selection.GetSelectedNode();
453						if (lastCreatedNode && lastCreatedNode.nodeType == 1)
454						{
455							_this.editor.lastCreatedId = Math.round(Math.random() * 1000000);
456							lastCreatedNode.setAttribute('data-bx-last-created-id', _this.editor.lastCreatedId);
457						}
458
459					}, 10);
460				},
461
462				state: function(action, value, tagName, arStyle, cssClass)
463				{
464					var
465						doc = _this.editor.GetIframeDoc(),
466						aliasTagName = TAG_ALIAS[tagName] || tagName;
467
468					// Check whether the document contains a node with the desired tagName
469					if (
470						!_this.editor.util.DocumentHasTag(doc, tagName)
471						&&
472						(tagName != aliasTagName && !_this.editor.util.DocumentHasTag(doc, aliasTagName))
473						)
474					{
475						return false;
476					}
477
478					var range = _this.editor.selection.GetRange();
479					if (!range)
480					{
481						return false;
482					}
483
484					var applier = getStyler(tagName, arStyle, cssClass);
485					return applier.IsAppliedToRange(range, false);
486				},
487
488				value: BX.DoNothing
489			};
490		},
491
492		/*
493		* TODO: 1. Clean useless spans when H1-H6 are created
494		* */
495		GetFormatBlock: function()
496		{
497			var
498				_this = this,
499				DEFAULT_NODE_NAME = "DIV",
500				blockTags = _this.editor.GetBlockTags(),
501				nestedBlockTags = _this.editor.NESTED_BLOCK_TAGS;
502
503			/**
504			 * Remove similiar classes (based on classRegExp)
505			 * and add the desired class name
506			 */
507			function _addClass(element, className, classRegExp)
508			{
509				if (element.className)
510				{
511					_removeClass(element, classRegExp);
512					element.className += " " + className;
513				}
514				else
515				{
516					element.className = className;
517				}
518			}
519
520			function _removeClass(element, classRegExp)
521			{
522				element.className = element.className.replace(classRegExp, "");
523			}
524
525			/**
526			 * Adds line breaks before and after the given node if the previous and next siblings
527			 * aren't already causing a visual line break (block element or <br>)
528			 */
529			function addBrBeforeAndAfter(node)
530			{
531				var
532					nextSibling = _this.editor.util.GetNextNotEmptySibling(node),
533					previousSibling = _this.editor.util.GetPreviousNotEmptySibling(node);
534
535				if (nextSibling && !isBrOrBlockElement(nextSibling))
536				{
537					node.parentNode.insertBefore(_this.document.createElement("BR"), nextSibling);
538				}
539
540				if (previousSibling && !isBrOrBlockElement(previousSibling))
541				{
542					node.parentNode.insertBefore(_this.document.createElement("BR"), node);
543				}
544			}
545
546			// Removes line breaks before and after the given node
547			function removeBrBeforeAndAfter(node)
548			{
549				var
550					nextSibling = _this.editor.util.GetNextNotEmptySibling(node),
551					previousSibling = _this.editor.util.GetPreviousNotEmptySibling(node);
552
553				if (nextSibling && nextSibling.nodeName === "BR")
554				{
555					nextSibling.parentNode.removeChild(nextSibling);
556				}
557
558				if (previousSibling && previousSibling.nodeName === "BR")
559				{
560					previousSibling.parentNode.removeChild(previousSibling);
561				}
562			}
563
564			function removeLastChildBr(node)
565			{
566				var lastChild = node.lastChild;
567				if (lastChild && lastChild.nodeName === "BR")
568				{
569					lastChild.parentNode.removeChild(lastChild);
570				}
571			}
572
573			function isBrOrBlockElement(element)
574			{
575				return element.nodeName === "BR" || _this.editor.util.IsBlockElement(element);
576			}
577
578			// Execute native query command
579			function execNativeCommand(command, nodeName, className, style)
580			{
581				if (className || style)
582				{
583//					var eventListener = dom.observe(doc, "DOMNodeInserted", function(event)
584//					{
585//						var target = event.target,
586//							displayStyle;
587//						if (target.nodeType !== 1 /* element node */)
588//							return;
589//
590//						displayStyle = dom.getStyle("display").from(target);
591//						if (displayStyle.substr(0, 6) !== "inline")
592//						{
593//							// Make sure that only block elements receive the given class
594//							target.className += " " + className;
595//						}
596//					});
597				}
598				_this.document.execCommand(command, false, nodeName);
599//				if (eventListener)
600//					eventListener.stop();
601			}
602
603			function _hasClasses(element)
604			{
605				return !element.className || BX.util.trim(element.className) === '';
606			}
607
608			function applyBlockElement(node, newNodeName, className, arStyle)
609			{
610				// Rename current block element to new block element and add class
611				if (newNodeName)
612				{
613					if (node.nodeName !== newNodeName)
614					{
615						node = _this.editor.util.RenameNode(node, newNodeName);
616					}
617					if (className)
618					{
619						node.className = className;
620					}
621					if (arStyle && arStyle.length > 0)
622					{
623						_this.editor.util.SetCss(node, arStyle);
624					}
625				}
626				else // Get rid of node
627				{
628					// Insert a line break afterwards and beforewards when there are siblings
629					// that are not of type line break or block element
630					addBrBeforeAndAfter(node);
631					_this.editor.util.ReplaceWithOwnChildren(node);
632				}
633			}
634
635			return {
636				exec: function(command, nodeName, className, arStyle, params)
637				{
638					params = params || {};
639					nodeName = typeof nodeName === "string" ? nodeName.toUpperCase() : nodeName;
640					var
641						childBlockNodes, i, createdBlockNodes,
642						range = params.range || _this.editor.selection.GetRange(),
643						blockElement = nodeName ? _this.actions.formatBlock.state(command, nodeName, className, arStyle) : false,
644						selectedNode;
645
646					if (params.range)
647						_this.editor.selection.RestoreBookmark();
648
649					if (blockElement) // Same block element
650					{
651						var wholeBlockSelected = range.startContainer == blockElement.firstChild && range.endContainer == blockElement.lastChild;
652
653						// Divs and blockquotes can be inside each other
654						if (nodeName && BX.util.in_array(nodeName, nestedBlockTags) && params.nestedBlocks !== false)
655						{
656							// create new div
657							blockElement = _this.document.createElement(nodeName || DEFAULT_NODE_NAME);
658							if (className)
659							{
660								blockElement.className = className;
661							}
662
663							// Select line with wrap
664							_this.editor.selection.Surround(blockElement);
665							_this.editor.selection.SelectNode(blockElement);
666							_this.editor.util.SetCss(blockElement, arStyle);
667
668							var nextBr = _this.editor.util.GetNextNotEmptySibling(blockElement);
669							if (nextBr && nextBr.nodeName == 'BR')
670								BX.remove(nextBr);
671
672							setTimeout(function()
673							{
674								if (blockElement)
675								{
676									_this.editor.selection.SelectNode(blockElement);
677								}
678							}, 50);
679						}
680						else if (params && params.splitBlock && !wholeBlockSelected)
681						{
682							var childBlock = _this.document.createElement(nodeName || DEFAULT_NODE_NAME);
683
684							_this.editor.selection.Surround(childBlock);
685							_this.editor.selection.SelectNode(childBlock);
686
687							if (className)
688								childBlock.className = className;
689
690							_this.editor.util.SetCss(childBlock, arStyle);
691
692							if (blockElement && blockElement.lastChild)
693							{
694								var _range = range.cloneRange();
695								_range.setStartAfter(childBlock);
696								_range.setEndAfter(blockElement.lastChild);
697
698								var afterBlock = blockElement.cloneNode(false);
699								_this.editor.selection.SetSelection(_range);
700								_this.editor.selection.Surround(afterBlock);
701
702								var firstBr = _this.editor.selection._GetNonTextFirstChild(afterBlock);
703								if (firstBr && firstBr.nodeName == 'BR')
704									BX.remove(firstBr);
705							}
706
707							_this.editor.selection.SetSelection(range);
708
709							setTimeout(function()
710							{
711								if (childBlock)
712								{
713									_this.editor.selection.SelectNode(childBlock);
714								}
715							}, 50);
716						}
717						else
718						{
719							_this.editor.util.SetCss(blockElement, arStyle);
720							if (className)
721							{
722								blockElement.className = className;
723
724								// Bug workaround for bug with rendering in Firefox
725								if (BX.browser.IsFirefox())
726								{
727									var tmpId = "bx-editor-temp-" + Math.round(Math.random() * 1000000);
728									blockElement.id = tmpId;
729									blockElement.parentNode.innerHTML = blockElement.parentNode.innerHTML;
730									blockElement = _this.editor.GetIframeElement(tmpId);
731									if (blockElement)
732										blockElement.removeAttribute("id");
733								}
734							}
735
736							setTimeout(function()
737							{
738								if (blockElement)
739								{
740									_this.editor.selection.SelectNode(blockElement);
741								}
742							}, 50);
743						}
744					}
745					else
746					{
747						// Find other block element and rename it (<h2></h2> => <h1></h1>)
748						if (nodeName === null || BX.util.in_array(nodeName, blockTags))
749						{
750							blockElement = false;
751							selectedNode = _this.editor.selection.GetSelectedNode();
752
753							if (selectedNode)
754							{
755								if (selectedNode.nodeType == 1 && BX.util.in_array(selectedNode.nodeName, blockTags))
756								{
757									blockElement = selectedNode;
758								}
759								else
760								{
761									blockElement = BX.findParent(selectedNode, function(n)
762									{
763										return BX.util.in_array(n.nodeName, blockTags);
764									}, _this.document.body);
765								}
766							}
767							else
768							{
769								var
770									commonAncestor = _this.editor.selection.GetCommonAncestorForRange(range);
771
772								if (commonAncestor && commonAncestor.nodeName !== 'BODY' &&
773									BX.util.in_array(commonAncestor.nodeName, blockTags))
774								{
775									blockElement = commonAncestor;
776								}
777							}
778
779							if (blockElement && !_this.actions.quote.checkNode(blockElement))
780							{
781								_this.editor.selection.ExecuteAndRestoreSimple(function()
782								{
783									applyBlockElement(blockElement, nodeName, className, arStyle);
784								});
785								return true;
786							}
787						}
788
789						blockElement = BX.create(nodeName || DEFAULT_NODE_NAME, {}, _this.document);
790						if (className)
791						{
792							blockElement.className = className;
793						}
794						_this.editor.util.SetCss(blockElement, arStyle);
795
796						if (range.collapsed || selectedNode && selectedNode.nodeType == 3 /* text node*/)
797						{
798							// Select node
799							_this.editor.selection.SelectNode(selectedNode);
800						}
801						else if (range.collapsed)
802						{
803							// Select line with wrap
804							_this.editor.selection.SelectLine();
805						}
806
807						_this.editor.selection.Surround(blockElement, range);
808						removeBrBeforeAndAfter(blockElement);
809						removeLastChildBr(blockElement);
810
811						// Used in align action
812						if (params.leaveChilds)
813						{
814							return blockElement;
815						}
816
817						if (nodeName && !BX.util.in_array(nodeName, nestedBlockTags))
818						{
819							range = _this.editor.selection.GetRange();
820							createdBlockNodes = range.getNodes([1]);
821
822							// 1. Find all child "P" and remove them
823							if (nodeName == 'P')
824							{
825								childBlockNodes = blockElement.getElementsByTagName(nodeName);
826								// Clean empty bogus H1, H2, H3...
827								for (i = 0; i < createdBlockNodes.length; i++)
828								{
829									if (arStyle && _this.editor.util.CheckCss(createdBlockNodes[i], arStyle, false) && _this.editor.util.IsEmptyNode(createdBlockNodes[i], true, true))
830									{
831										BX.remove(createdBlockNodes[i]);
832									}
833								}
834
835								while (childBlockNodes[0])
836								{
837									addBrBeforeAndAfter(childBlockNodes[0]);
838									_this.editor.util.ReplaceWithOwnChildren(childBlockNodes[0]);
839								}
840							}
841							else if (nodeName.substr(0, 1) == 'H')
842							{
843								// Clean empty bogus H1, H2, H3...
844								for (i = 0; i < createdBlockNodes.length; i++)
845								{
846									if (createdBlockNodes[i].nodeName !== nodeName && _this.editor.util.IsEmptyNode(createdBlockNodes[i], true, true))
847									{
848										BX.remove(createdBlockNodes[i]);
849									}
850								}
851
852								var childHeaders = BX.findChild(blockElement, function(n)
853								{
854									return BX.util.in_array(n.nodeName, blockTags) && n.nodeName.substr(0, 1) == 'H';
855								}, true, true);
856
857								for (i = 0; i < childHeaders.length; i++)
858								{
859									addBrBeforeAndAfter(childHeaders[i]);
860									_this.editor.util.ReplaceWithOwnChildren(childHeaders[i]);
861								}
862							}
863						}
864
865						if (blockElement && params.bxTagParams && typeof params.bxTagParams == 'object')
866						{
867							_this.editor.SetBxTag(blockElement, params.bxTagParams);
868						}
869
870						if (blockElement && blockElement.parentNode)
871						{
872							var parent = blockElement.parentNode;
873							if (parent.nodeName == 'UL' || parent.nodeName == 'OL')
874							{
875								// 1. Clean empty LI before and after
876								var li = _this.editor.util.GetPreviousNotEmptySibling(blockElement);
877								if (_this.editor.util.IsEmptyLi(li))
878								{
879									BX.remove(li);
880								}
881								li = _this.editor.util.GetNextNotEmptySibling(blockElement);
882								if (_this.editor.util.IsEmptyLi(li))
883								{
884									BX.remove(li);
885								}
886
887								// 2. If parent list doesn't have other items - put it inside blockquote
888								if (!_this.editor.util.GetPreviousNotEmptySibling(blockElement) && !_this.editor.util.GetNextNotEmptySibling(blockElement))
889								{
890									var blockElementNew = blockElement.cloneNode(false);
891									parent.parentNode.insertBefore(blockElementNew, parent);
892									_this.editor.util.ReplaceWithOwnChildren(blockElement);
893									blockElementNew.appendChild(parent);
894								}
895							}
896
897							if (blockElement.firstChild && blockElement.firstChild.nodeName == 'BLOCKQUOTE')
898							{
899								var prev = _this.editor.util.GetPreviousNotEmptySibling(blockElement);
900								if (prev && prev.nodeName == 'BLOCKQUOTE' && _this.editor.util.IsEmptyNode(prev))
901								{
902									BX.remove(prev);
903								}
904							}
905
906							if ((blockElement.nodeName == 'BLOCKQUOTE' || blockElement.nodeName == 'PRE') && _this.editor.util.IsEmptyNode(blockElement))
907							{
908								blockElement.innerHTML = '';
909								var br = _this.document.createElement("br");
910								blockElement.appendChild(br);
911								_this.editor.selection.SetAfter(br);
912							}
913						}
914
915						setTimeout(function()
916						{
917							if (blockElement)
918							{
919								_this.editor.selection.SelectNode(blockElement);
920							}
921						}, 10);
922
923						return true;
924					}
925				},
926
927				state: function(command, nodeName, className, style)
928				{
929					nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
930					var
931						result = false,
932						selectedNode = _this.editor.selection.GetSelectedNode();
933
934					if (selectedNode && selectedNode.nodeName)
935					{
936						if (selectedNode.nodeName != nodeName)
937						{
938							selectedNode = BX.findParent(selectedNode, function(n)
939							{
940								return n.nodeName == nodeName;
941							}, _this.document.body);
942						}
943						result = (selectedNode && selectedNode.tagName == nodeName) ? selectedNode : false;
944					}
945					else
946					{
947						var
948							range = _this.editor.selection.GetRange(),
949							commonAncestor = _this.editor.selection.GetCommonAncestorForRange(range);
950
951						if (commonAncestor.nodeName == nodeName)
952						{
953							result = commonAncestor;
954						}
955					}
956					return result;
957				},
958
959				value: BX.DoNothing,
960
961				removeBrBeforeAndAfter: removeBrBeforeAndAfter,
962				addBrBeforeAndAfter: addBrBeforeAndAfter
963			};
964		},
965
966		GetRemoveFormat: function()
967		{
968			var
969				FORMAT_NODES_INLINE = {
970					"B": 1,
971					"STRONG": 1,
972					"I": 1,
973					"EM": 1,
974					"U": 1,
975					"DEL": 1,
976					"S": 1,
977					"STRIKE": 1,
978					"A": 1,
979					"SPAN" : 1,
980					"CODE" : 1,
981					"NOBR" : 1,
982					"Q" : 1,
983					"FONT" : 1,
984					"CENTER": 1,
985					"CITE": 1
986				},
987				FORMAT_NODES_BLOCK = {
988					"H1": 1,
989					"H2": 1,
990					"H3": 1,
991					"H4": 1,
992					"H5": 1,
993					"H6": 1,
994					"DIV": 1,
995					"P": 1,
996					"LI": 1,
997					"UL" : 1,
998					"OL" : 1,
999					"MENU" : 1,
1000					"BLOCKQUOTE": 1,
1001					"PRE": 1
1002				},
1003				_this = this;
1004
1005			function checkAndCleanNode(node, doc)
1006			{
1007				if (!node)
1008					return;
1009				var nodeName = node.nodeName;
1010				if (FORMAT_NODES_INLINE[nodeName])
1011				{
1012					_this.editor.util.ReplaceWithOwnChildren(node);
1013				}
1014				else if (FORMAT_NODES_BLOCK[nodeName])
1015				{
1016					_this.actions.formatBlock.addBrBeforeAndAfter(node);
1017					_this.editor.util.ReplaceWithOwnChildren(node);
1018				}
1019				else
1020				{
1021					node.removeAttribute("style");
1022					node.removeAttribute("class");
1023					node.removeAttribute("align");
1024
1025					if (_this.editor.bbCode && node.nodeName == 'TABLE')
1026					{
1027						node.removeAttribute('align');
1028					}
1029				}
1030			}
1031
1032			function checkTableNode(node)
1033			{
1034				return BX.findParent(node, function(n)
1035				{
1036					return n.nodeName == "TABLE";
1037				}, _this.editor.GetIframeDoc().body);
1038			}
1039
1040			function getUnitaryParent(textNode)
1041			{
1042				var
1043					prevSibling, nextSibling,
1044					parent = textNode.parentNode;
1045
1046				while (parent && parent.nodeName !== 'BODY')
1047				{
1048					prevSibling = parent.previousSibling && !_this.editor.util.IsEmptyNode(parent.previousSibling);
1049					nextSibling = parent.nextSibling && !_this.editor.util.IsEmptyNode(parent.nextSibling);
1050
1051					if (prevSibling || nextSibling || parent.parentNode.nodeName == 'BODY')
1052					{
1053						break;
1054					}
1055					parent = parent.parentNode;
1056				}
1057				return parent;
1058			}
1059
1060			function checkParentList(node)
1061			{
1062				var
1063					doc = _this.editor.GetIframeDoc(),
1064					list = BX.findParent(node, function(n)
1065					{
1066						return n.nodeName == "UL" || n.nodeName == "OL" || n.nodeName == "MENU";
1067					}, doc.body);
1068
1069				if (list)
1070				{
1071					var
1072						i, child,
1073						listBefore = list.cloneNode(false),
1074						listAfter = list.cloneNode(false),
1075						before = true;
1076
1077					BX.cleanNode(listBefore);
1078					BX.cleanNode(listAfter);
1079
1080					for (i = 0; i < list.childNodes.length; i++)
1081					{
1082						child = list.childNodes[i];
1083						if (child == node)
1084						{
1085							before = false;
1086						}
1087
1088						if (child.nodeName == 'LI')
1089						{
1090							if (!_this.editor.util.IsEmptyNode(child, true, true))
1091							{
1092								(before ? listBefore : listAfter).appendChild(child.cloneNode(true));
1093							}
1094						}
1095					}
1096
1097					if (listBefore.childNodes.length > 0)
1098					{
1099						list.parentNode.insertBefore(listBefore, list);
1100					}
1101					list.parentNode.insertBefore(node, list);
1102					if (listAfter.childNodes.length > 0)
1103					{
1104						list.parentNode.insertBefore(listAfter, list);
1105					}
1106					BX.remove(list);
1107					return true;
1108				}
1109				return false;
1110			}
1111
1112			function cleanNodes(nodes, doc)
1113			{
1114				if (nodes && nodes.length > 0)
1115				{
1116					var i, len, sorted = [];
1117					for (i = 0, len = nodes.length; i < len; i++)
1118					{
1119						if (!_this.editor.util.CheckSurrogateNode(nodes[i]))
1120						{
1121							sorted.push({node: nodes[i], nesting: _this.editor.util.GetNodeDomOffset(nodes[i])});
1122						}
1123					}
1124					sorted = sorted.sort(function(a, b){return b.nesting - a.nesting});
1125					for (i = 0, len = sorted.length; i < len; i++)
1126					{
1127						checkAndCleanNode(sorted[i].node, doc);
1128					}
1129				}
1130			}
1131
1132			function _selectAndGetNodes(node)
1133			{
1134				var
1135					i, found = false,
1136					range = _this.editor.selection.SelectNode(node),
1137					nodes = range.getNodes([1]);
1138
1139				if (node.nodeType == 1)
1140				{
1141					for (i = 0; i < nodes.length; i++)
1142					{
1143						if (nodes[i] == node)
1144						{
1145							found = true;
1146							break;
1147						}
1148					}
1149					if (!found)
1150					{
1151						nodes = [node].concat(nodes);
1152					}
1153				}
1154
1155				if (!nodes || typeof nodes != 'object' || nodes.length == 0)
1156				{
1157					nodes = [node];
1158				}
1159				return nodes;
1160			}
1161
1162			function getNodesFromTo(nodeStart, nodeEnd)
1163			{
1164				var list = [];
1165				if (nodeStart && (!nodeEnd || nodeStart == nodeEnd))
1166				{
1167					list.push(nodeStart);
1168				}
1169				else if (!nodeStart && nodeEnd)
1170				{
1171					list.push(nodeEnd);
1172				}
1173
1174				return list;
1175			}
1176
1177
1178			return {
1179				exec: function(action, value)
1180				{
1181					var range = _this.editor.selection.GetRange();
1182
1183					if (!range || _this.editor.iframeView.IsEmpty())
1184						return;
1185
1186					var
1187						bSurround = true,
1188						i,
1189						textNodes, textNode, node, tmpNode,
1190						nodes = range.getNodes([1]),
1191						doc = _this.editor.GetIframeDoc();
1192
1193					// Range is collapsed or text node is selected
1194					if (nodes.length == 0)
1195					{
1196						textNodes = range.getNodes([3]);
1197
1198						if (textNodes && textNodes.length == 1)
1199						{
1200							textNode = textNodes[0];
1201						}
1202
1203						if (!textNode && range.startContainer == range.endContainer)
1204						{
1205							if (range.startContainer.nodeType == 3)
1206							{
1207								textNode = range.startContainer;
1208							}
1209							else
1210							{
1211								bSurround = false;
1212								nodes = _selectAndGetNodes(range.startContainer);
1213							}
1214						}
1215
1216						if (textNode && nodes.length == 0)
1217						{
1218							node = getUnitaryParent(textNode);
1219							if (node && (node.nodeName != 'BODY' || range.collapsed))
1220							{
1221								bSurround = false;
1222								nodes = _selectAndGetNodes(node);
1223							}
1224						}
1225					}
1226					else
1227					{
1228						var
1229							updateSel = false,
1230							clearRanges = [],
1231							startTableCheck = checkTableNode(range.startContainer),
1232							endTableCheck = checkTableNode(range.endContainer);
1233
1234						if (startTableCheck)
1235						{
1236							clearRanges.push(
1237								{
1238									startContainer: range.startContainer,
1239									startOffset: range.startOffset,
1240									end: startTableCheck
1241								}
1242							);
1243
1244							range.setStartAfter(startTableCheck);
1245							updateSel = true;
1246						}
1247
1248						if (endTableCheck)
1249						{
1250							updateSel = true;
1251							clearRanges.push(
1252								{
1253									start: endTableCheck,
1254									endContainer: range.endContainer,
1255									endOffset: range.endOffset
1256								}
1257							);
1258							range.setEndBefore(endTableCheck);
1259						}
1260
1261
1262						var
1263							startList = _this.editor.util.FindParentEx(range.startContainer, function(n){return n.nodeName == "UL" || n.nodeName == "OL" || n.nodeName == "MENU";}, doc.body),
1264							endList = _this.editor.util.FindParentEx(range.endContainer, function(n){return n.nodeName == "UL" || n.nodeName == "OL" || n.nodeName == "MENU";}, doc.body);
1265							//listNodes = getNodesFromTo(startList, endList);
1266
1267						if (startList)
1268						{
1269							range.setStartBefore(startList);
1270							if (!endList)
1271								range.setEndAfter(startList);
1272							updateSel = true;
1273						}
1274
1275						if (endList)
1276						{
1277							updateSel = true;
1278							range.setEndAfter(endList);
1279							if (!startList)
1280								range.setStartBefore(endList);
1281						}
1282
1283						if (updateSel)
1284						{
1285							_this.editor.selection.SetSelection(range);
1286							nodes = range.getNodes([1]);
1287						}
1288					}
1289
1290					if (bSurround)
1291					{
1292						tmpNode = doc.createElement("span");
1293						_this.editor.selection.Surround(tmpNode, range);
1294						nodes = _selectAndGetNodes(tmpNode);
1295					}
1296
1297					if (nodes && nodes.length > 0)
1298					{
1299						_this.editor.selection.ExecuteAndRestoreSimple(function()
1300						{
1301							cleanNodes(nodes, doc);
1302						});
1303					}
1304
1305					if (clearRanges && clearRanges.length > 0)
1306					{
1307						var
1308							_range = range.cloneRange();
1309
1310						for (i = 0; i < clearRanges.length; i++)
1311						{
1312							if (clearRanges[i].start)
1313							{
1314								_range.setStartBefore(clearRanges[i].start);
1315							}
1316							else
1317							{
1318								_range.setStart(clearRanges[i].startContainer, clearRanges[i].startOffset);
1319							}
1320							if (clearRanges[i].end)
1321							{
1322								_range.setEndAfter(clearRanges[i].end);
1323							}
1324							else
1325							{
1326								_range.setEnd(clearRanges[i].endContainer,  clearRanges[i].endOffset);
1327							}
1328							_this.editor.selection.SetSelection(_range);
1329							cleanNodes(_range.getNodes([1]), doc);
1330						}
1331
1332						_this.editor.selection.SetSelection(range);
1333					}
1334
1335					if (bSurround && tmpNode && tmpNode.parentNode)
1336					{
1337						if (checkParentList(tmpNode))
1338						{
1339							_this.editor.selection.SelectNode(tmpNode);
1340						}
1341
1342						_this.editor.selection.ExecuteAndRestoreSimple(function()
1343						{
1344							_this.editor.util.ReplaceWithOwnChildren(tmpNode);
1345						});
1346					}
1347
1348					if (!_this.editor.iframeView.IsEmpty(true))
1349					{
1350						_this.actions.formatBlock.exec('formatBlock', null);
1351					}
1352				},
1353				state: BX.DoNothing,
1354				value: BX.DoNothing
1355			};
1356		},
1357
1358		GetBold: function()
1359		{
1360			var _this = this;
1361			return {
1362				exec: function(action, value)
1363				{
1364					// Iframe
1365					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
1366					{
1367						return _this.actions.formatInline.exec(action, value, "b");
1368					}
1369					else // BBCode mode
1370					{
1371						return _this.actions.formatBbCode.exec(action, {tag: 'B'});
1372					}
1373				},
1374				state: function(action, value)
1375				{
1376					return _this.actions.formatInline.state(action, value, "b");
1377				},
1378				value: BX.DoNothing
1379			};
1380		},
1381
1382		GetItalic: function()
1383		{
1384			var _this = this;
1385			return {
1386				exec: function(action, value)
1387				{
1388					// Iframe
1389					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
1390					{
1391						return _this.actions.formatInline.exec(action, value, "i");
1392					}
1393					else
1394					{
1395						return _this.actions.formatBbCode.exec(action, {tag: 'I'});
1396					}
1397				},
1398				state: function(action, value)
1399				{
1400					return _this.actions.formatInline.state(action, value, "i");
1401				},
1402				value: BX.DoNothing
1403			};
1404		},
1405
1406		GetUnderline: function()
1407		{
1408			var _this = this;
1409			return {
1410				exec: function(action, value)
1411				{
1412					// Iframe
1413					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
1414					{
1415						return _this.actions.formatInline.exec(action, value, "u");
1416					}
1417					else
1418					{
1419						return _this.actions.formatBbCode.exec(action, {tag: 'U'});
1420					}
1421				},
1422				state: function(action, value)
1423				{
1424					return _this.actions.formatInline.state(action, value, "u");
1425				},
1426				value: BX.DoNothing
1427			};
1428		},
1429
1430		GetStrikeout: function()
1431		{
1432			var _this = this;
1433			return {
1434				exec: function(action, value)
1435				{
1436					// Iframe
1437					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
1438					{
1439						return _this.actions.formatInline.exec(action, value, "del");
1440					}
1441					else
1442					{
1443						return _this.actions.formatBbCode.exec(action, {tag: 'S'});
1444					}
1445				},
1446				state: function(action, value)
1447				{
1448					return _this.actions.formatInline.state(action, value, "del");
1449				},
1450				value: BX.DoNothing
1451			};
1452		},
1453
1454		GetFontSize: function()
1455		{
1456			var _this = this;
1457			return {
1458				exec: function(action, value)
1459				{
1460					var res;
1461					// Iframe
1462					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
1463					{
1464						if (value > 0) // Format
1465							res = _this.actions.formatInline.exec(action, value, "span", {fontSize: value + 'pt'});
1466						else // Clear font-size format
1467							res = _this.actions.formatInline.exec(action, value, "span", {fontSize: null}, null, {bClear: true});
1468					}
1469					else // textarea + bbcode
1470					{
1471						res = _this.actions.formatBbCode.exec(action, {tag: 'SIZE', value: value + 'pt'});
1472					}
1473					return res;
1474				},
1475
1476				state: function(action, value)
1477				{
1478					return _this.actions.formatInline.state(action, value, "span", {fontSize: value + 'pt'});
1479				},
1480
1481				value: BX.DoNothing
1482			};
1483		},
1484
1485		GetForeColor: function()
1486		{
1487			var _this = this;
1488
1489			function checkListItemColor()
1490			{
1491				var
1492					doc = _this.editor.GetIframeDoc(),
1493					i, item, res = [],
1494					range = _this.editor.selection.GetRange();
1495
1496				if (range)
1497				{
1498					var nodes = range.getNodes([1]);
1499
1500					if (nodes.length == 0 && range.startContainer == range.endContainer &&  range.startContainer.nodeName != 'BODY')
1501					{
1502						nodes = [range.startContainer];
1503					}
1504
1505					for (i = 0; i < nodes.length; i++)
1506					{
1507						item = BX.findParent(nodes[i], function(n){
1508							return n.nodeName == 'LI' && n.style && n.style.color;
1509						}, doc.body);
1510
1511						if (item)
1512						{
1513							res.push(item);
1514						}
1515					}
1516				}
1517
1518				return res.length === 0 ? false : res;
1519			}
1520
1521			function checkAndApplyColorListItems(value)
1522			{
1523				var
1524					doc = _this.editor.GetIframeDoc(),
1525					node = _this.editor.selection.GetSelectedNode(),
1526					i, j, spans, range, span, li,
1527					nodes;
1528
1529				if (node && (node.nodeType === 3 || node.nodeName == 'SPAN'))
1530				{
1531					span = node.nodeName == 'SPAN' ? node : BX.findParent(node, {tag: 'span'}, doc.body);
1532					if (span && span.style.color)
1533					{
1534						li = BX.findParent(span, {tag: 'li'}, doc.body);
1535						if (li)
1536						{
1537							if (li.childNodes.length == 1 && li.firstChild == span)
1538							{
1539								_this.editor.selection.ExecuteAndRestoreSimple(function()
1540								{
1541									li.style.color = value;
1542									span.style.color = '';
1543									if (BX.util.trim(span.style.cssText) == '')
1544									{
1545										_this.editor.util.ReplaceWithOwnChildren(span);
1546									}
1547								});
1548							}
1549						}
1550					}
1551				}
1552				else
1553				{
1554					if (!node)
1555					{
1556						range = _this.editor.selection.GetRange();
1557						nodes = range.getNodes([1]);
1558					}
1559					else
1560					{
1561						nodes = [node];
1562					}
1563
1564					for (i = 0; i < nodes.length; i++)
1565					{
1566						if (nodes[i] && nodes[i].nodeName == "LI" && !_this.editor.bbCode)
1567						{
1568							// 1. Add color to LI
1569							nodes[i].style.color = value;
1570							// Find and clear all spans created by action
1571							spans = BX.findChild(nodes[i], function(n)
1572							{
1573								return n.nodeName == "SPAN";
1574							}, true, true);
1575
1576							_this.editor.selection.ExecuteAndRestoreSimple(function()
1577							{
1578								for (j = 0; j < spans.length; j++)
1579								{
1580									if (spans[j] && spans[j].parentNode &&
1581										spans[j].parentNode.parentNode &&
1582										spans[j].parentNode.parentNode.parentNode)
1583									{
1584										try{
1585											spans[j].style.color = '';
1586											if (BX.util.trim(spans[j].style.cssText) == '')
1587											{
1588												_this.editor.util.ReplaceWithOwnChildren(spans[j]);
1589											}
1590										}
1591										catch(e)
1592										{}
1593									}
1594								}
1595							});
1596						}
1597					}
1598				}
1599			}
1600
1601			return {
1602				exec: function(action, value)
1603				{
1604					var res;
1605
1606					// Iframe
1607					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
1608					{
1609						if (value == '')
1610						{
1611							res = _this.actions.formatInline.exec(action, value, "span", {color: null}, null, {bClear: true});
1612						}
1613						else
1614						{
1615							res =  _this.actions.formatInline.exec(action, value, "span", {color: value});
1616							checkAndApplyColorListItems(value);
1617						}
1618					}
1619					else if (value) // textarea + bbcode
1620					{
1621						res = _this.actions.formatBbCode.exec(action, {tag: 'COLOR', value: value});
1622					}
1623
1624					return res;
1625				},
1626
1627				state: function(action, value)
1628				{
1629					var state = _this.actions.formatInline.state(action, value, "span", {color: value});
1630					if (!state)
1631					{
1632						state = checkListItemColor();
1633					}
1634
1635					return state;
1636				},
1637
1638				value: BX.DoNothing
1639			};
1640		},
1641
1642		GetBackgroundColor: function()
1643		{
1644			var _this = this;
1645
1646			return {
1647				exec: function(action, value)
1648				{
1649					var res;
1650					if (value == '')
1651					{
1652						res =  _this.actions.formatInline.exec(action, value, "span", {backgroundColor: null}, null, {bClear: true});
1653					}
1654					else
1655					{
1656						res =  _this.actions.formatInline.exec(action, value, "span", {backgroundColor: value});
1657					}
1658					return res;
1659				},
1660
1661				state: function(action, value)
1662				{
1663					return _this.actions.formatInline.state(action, value, "span", {backgroundColor: value});
1664				},
1665
1666				value: BX.DoNothing
1667			};
1668		},
1669
1670		GetCreateLink: function()
1671		{
1672			var
1673				ATTRIBUTES = ['title', 'id', 'name', 'target', 'rel'],
1674				_this = this;
1675
1676			return {
1677				exec: function(action, value)
1678				{
1679					// Only for bbCode == true
1680					if (_this.editor.bbCode && _this.editor.synchro.IsFocusedOnTextarea())
1681					{
1682						_this.editor.textareaView.Focus();
1683						var linkHtml = "[URL=" + _this.editor.util.spaceUrlEncode(value.href) + "]" + (value.text || value.href) + "[/URL]";
1684						_this.editor.textareaView.WrapWith(false, false, linkHtml);
1685					}
1686					else
1687					{
1688						_this.editor.iframeView.Focus();
1689
1690						var
1691							nodeToSetCarret,
1692							params = (value && typeof(value) === "object") ? value : {href: value},
1693							i, link, linksCount = 0, lastLink,
1694							links;
1695
1696						function applyAttributes(link, params)
1697						{
1698							var attr;
1699							link.removeAttribute("class");
1700							link.removeAttribute("target");
1701
1702							for (attr in params)
1703							{
1704								if (params.hasOwnProperty(attr) && BX.util.in_array(attr, ATTRIBUTES))
1705								{
1706									if (params[attr] == '' || params[attr] == undefined)
1707									{
1708										link.removeAttribute(attr);
1709									}
1710									else
1711									{
1712										link.setAttribute(attr, params[attr]);
1713									}
1714								}
1715							}
1716							if (params.className)
1717								link.className = params.className;
1718							link.href = _this.editor.util.spaceUrlEncode(params.href) || '';
1719
1720							if (params.noindex)
1721							{
1722								link.setAttribute("data-bx-noindex", "Y");
1723							}
1724							else
1725							{
1726								link.removeAttribute("data-bx-noindex");
1727							}
1728						}
1729
1730						links = value.node ? [value.node] : _this.actions.formatInline.state(action, value, "a");
1731						if (links)
1732						{
1733							// Selection contains links
1734							for (i = 0; i < links.length; i++)
1735							{
1736								link = links[i];
1737								if (link)
1738								{
1739									applyAttributes(link, params);
1740									lastLink = link;
1741									linksCount++;
1742								}
1743							}
1744
1745							if (linksCount === 1 && lastLink && (lastLink.querySelector && !lastLink.querySelector("*")) && params.text != '')
1746							{
1747								_this.editor.util.SetTextContent(lastLink, params.text);
1748							}
1749							nodeToSetCarret = lastLink;
1750
1751							if (nodeToSetCarret)
1752								_this.editor.selection.SetAfter(nodeToSetCarret);
1753
1754							setTimeout(function()
1755							{
1756								if (nodeToSetCarret)
1757									_this.editor.selection.SetAfter(nodeToSetCarret);
1758							}, 10);
1759						}
1760						else
1761						{
1762
1763							var
1764								tmpClass = "_bx-editor-temp-" + Math.round(Math.random() * 1000000),
1765								invisText,
1766								isEmpty,
1767								textContent;
1768
1769							// Create LINKS
1770							_this.actions.formatInline.exec(action, value, "A", false, tmpClass);
1771
1772							if (_this.document.querySelectorAll)
1773							{
1774								links = _this.document.querySelectorAll("A." + tmpClass);
1775							}
1776							else
1777							{
1778								links = [];
1779							}
1780
1781							for (i = 0; i < links.length; i++)
1782							{
1783								link = links[i];
1784								if (link)
1785								{
1786									applyAttributes(link, params);
1787								}
1788							}
1789
1790							nodeToSetCarret = link; // last link
1791
1792							if (links.length === 1)
1793							{
1794								textContent = _this.editor.util.GetTextContent(link);
1795								isEmpty = textContent === "" || textContent === _this.editor.INVISIBLE_SPACE;
1796
1797								if (textContent != params.text)
1798								{
1799									_this.editor.util.SetTextContent(link, params.text || params.href);
1800								}
1801
1802								if (link.querySelector && !link.querySelector("*") && isEmpty) // Link is empty
1803								{
1804									_this.editor.util.SetTextContent(link, params.text || params.href);
1805								}
1806							}
1807
1808							if (link)
1809							{
1810								if (link.nextSibling && link.nextSibling.nodeType == 3 && _this.editor.util.IsEmptyNode(link.nextSibling))
1811								{
1812									invisText = link.nextSibling;
1813								}
1814								else
1815								{
1816									invisText = _this.editor.util.GetInvisibleTextNode();
1817								}
1818								_this.editor.util.InsertAfter(invisText, link);
1819								nodeToSetCarret = invisText;
1820							}
1821
1822							if (nodeToSetCarret)
1823								_this.editor.selection.SetAfter(nodeToSetCarret);
1824
1825							setTimeout(function()
1826							{
1827								if (nodeToSetCarret)
1828									_this.editor.selection.SetAfter(nodeToSetCarret);
1829							}, 10);
1830						}
1831					}
1832				},
1833
1834				state: function(action, value)
1835				{
1836					return _this.actions.formatInline.state(action, value, "a");
1837				},
1838
1839				value: BX.DoNothing
1840			};
1841		},
1842
1843		GetRemoveLink: function()
1844		{
1845			var _this = this;
1846			return {
1847				exec: function(action, value)
1848				{
1849					_this.editor.iframeView.Focus();
1850
1851					var
1852						i, link, links;
1853
1854					if (value && typeof value == 'object')
1855					{
1856						links = value;
1857					}
1858					else
1859					{
1860						links = _this.actions.formatInline.state(action, value, "a");
1861					}
1862
1863					if (links)
1864					{
1865						// Selection contains links
1866						_this.editor.selection.ExecuteAndRestoreSimple(function()
1867						{
1868							for (i = 0; i < links.length; i++)
1869							{
1870								link = links[i];
1871								if (link)
1872								{
1873									_this.editor.util.ReplaceWithOwnChildren(links[i]);
1874								}
1875							}
1876						});
1877					}
1878				},
1879
1880				state: function(action, value)
1881				{
1882					//return _this.actions.formatInline.state(action, value, "a");
1883				},
1884				value: BX.DoNothing
1885			};
1886		},
1887
1888		GetInsertHTML: function()
1889		{
1890			var _this = this;
1891
1892			return {
1893				exec: function(action, html)
1894				{
1895					_this.editor.iframeView.Focus();
1896
1897					if (_this.IsSupportedByBrowser(action))
1898						_this.document.execCommand(action, false, html);
1899					else
1900						_this.editor.selection.InsertHTML(html);
1901				},
1902
1903				state: function()
1904				{
1905					return false;
1906				},
1907
1908				value: BX.DoNothing
1909			};
1910		},
1911
1912		GetInsertImage: function()
1913		{
1914			var
1915				ATTRIBUTES = ['title', 'alt', 'width', 'height', 'align'],
1916				_this = this;
1917
1918			return {
1919				exec: function(action, value)
1920				{
1921					if (value.src == '')
1922						return;
1923
1924					value.src = _this.editor.util.spaceUrlEncode(value.src);
1925
1926					// Only for bbCode == true
1927					if (_this.editor.bbCode && _this.editor.synchro.IsFocusedOnTextarea())
1928					{
1929						_this.editor.textareaView.Focus();
1930						var size = '';
1931						if (value.width)
1932							size += ' WIDTH=' + parseInt(value.width);
1933						if (value.height)
1934							size += ' HEIGHT=' + parseInt(value.height);
1935						var imgHtml = "[IMG" + size + "]" + value.src + "[/IMG]";
1936						_this.editor.textareaView.WrapWith(false, false, imgHtml);
1937						return;
1938					}
1939
1940					_this.editor.iframeView.Focus();
1941					var
1942						params = (value && typeof(value) === "object") ? value : {src: value},
1943						image = value.image || _this.actions.insertImage.state(action, value),
1944						invisText, sibl;
1945
1946					function applyAttributes(image, params)
1947					{
1948						var attr, appAttr;
1949						image.removeAttribute("class");
1950						image.setAttribute('data-bx-orig-src', params.src || '');
1951
1952						for (attr in params)
1953						{
1954							if (params.hasOwnProperty(attr) && BX.util.in_array(attr, ATTRIBUTES))
1955							{
1956								if (params[attr] == '' || params[attr] == undefined)
1957								{
1958									image.removeAttribute(attr);
1959								}
1960								else
1961								{
1962									appAttr = image.getAttribute('data-bx-app-ex-' + attr);
1963									if (!appAttr || _this.editor.phpParser.AdvancedPhpGetFragmentByCode(appAttr, true) != params[attr])
1964									{
1965										image.setAttribute(attr, params[attr]);
1966										if (appAttr)
1967										{
1968											image.removeAttribute('data-bx-app-ex-' + attr);
1969										}
1970									}
1971								}
1972							}
1973						}
1974
1975						if (params.className)
1976						{
1977							image.className = params.className;
1978						}
1979
1980						appAttr = image.getAttribute('data-bx-app-ex-src');
1981						if (!appAttr || _this.editor.phpParser.AdvancedPhpGetFragmentByCode(appAttr, true) != params.src)
1982						{
1983							image.src = params.src || '';
1984							if (appAttr)
1985							{
1986								image.removeAttribute('data-bx-app-ex-src');
1987							}
1988						}
1989
1990					}
1991
1992					if (!image)
1993					{
1994						image = _this.document.createElement('IMG');
1995						applyAttributes(image, params);
1996						_this.editor.selection.InsertNode(image);
1997					}
1998					else
1999					{
2000						applyAttributes(image, params);
2001					}
2002
2003					var
2004						nodeToSetCarret = image,
2005						parentLink = (image.parentNode && image.parentNode.nodeName == 'A') ? image.parentNode : null;
2006
2007					if (params.link)
2008					{
2009						if (parentLink)
2010						{
2011							// Just change url
2012							parentLink.href = params.link;
2013						}
2014						else
2015						{
2016							// Add surrounding link
2017							parentLink = _this.document.createElement('A');
2018							parentLink.href = params.link;
2019							image.parentNode.insertBefore(parentLink, image);
2020							parentLink.appendChild(image);
2021						}
2022						nodeToSetCarret = parentLink;
2023					}
2024					else if (parentLink)
2025					{
2026						// Remove parent link
2027						_this.editor.util.ReplaceWithOwnChildren(parentLink);
2028					}
2029
2030					// For IE it's impssible to set the caret after an <img> if it's the lastChild in the document
2031					if (BX.browser.IsIE())
2032					{
2033						_this.editor.selection.SetAfter(image);
2034						sibl = image.nextSibling;
2035						if (sibl && sibl.nodeType == 3 && _this.editor.util.IsEmptyNode(sibl))
2036							invisText = sibl;
2037						else
2038							invisText = _this.editor.util.GetInvisibleTextNode();
2039						_this.editor.selection.InsertNode(invisText);
2040						nodeToSetCarret = invisText;
2041					}
2042
2043					_this.editor.selection.SetAfter(nodeToSetCarret);
2044					_this.editor.util.Refresh();
2045				},
2046
2047				state: function()
2048				{
2049					var
2050						selectedNode,
2051						text,
2052						selectedImg;
2053
2054					if (!_this.editor.util.DocumentHasTag(_this.document, 'IMG'))
2055						return false;
2056
2057					selectedNode = _this.editor.selection.GetSelectedNode();
2058					if (!selectedNode)
2059						return false;
2060
2061					if (selectedNode.nodeName === 'IMG')
2062						return selectedNode;
2063
2064					if (selectedNode.nodeType !== 1 /* element node */)
2065						return false;
2066
2067					text = BX.util.trim(_this.editor.selection.GetText());
2068					if (text && text != _this.editor.INVISIBLE_SPACE)
2069						return false;
2070
2071					selectedImg = _this.editor.selection.GetNodes(1, function(node)
2072					{
2073						return node.nodeName === "IMG";
2074					});
2075
2076					// Works only for one image
2077					if (selectedImg.length !== 1)
2078						return false;
2079
2080					return selectedImg[0];
2081				},
2082
2083				value: BX.DoNothing
2084//				value: function(composer) {
2085//					var image = this.state(composer);
2086//					return image && image.src;
2087//				}
2088			};
2089		},
2090
2091		GetInsertLineBreak: function()
2092		{
2093			var
2094				_this = this,
2095				br = "<br>" + (BX.browser.IsOpera() ? " " : "");
2096
2097			return {
2098				exec: function(action)
2099				{
2100					if (_this.IsSupportedByBrowser(action))
2101					{
2102						_this.document.execCommand(action, false, null);
2103					}
2104					else
2105					{
2106						_this.actions.insertHTML.exec("insertHTML", br);
2107					}
2108
2109					if (BX.browser.IsChrome() || BX.browser.IsSafari() || BX.browser.IsIE10())
2110					{
2111						_this.editor.selection.ScrollIntoView();
2112					}
2113				},
2114
2115				state: BX.DoNothing,
2116				value: BX.DoNothing
2117			};
2118		},
2119
2120		GetInsertHr: function()
2121		{
2122			var
2123				_this = this,
2124				html = "<hr>" + (BX.browser.IsOpera() ? " " : "");
2125
2126			return {
2127				exec: function(action)
2128				{
2129					_this.actions.insertHTML.exec("insertHTML", html);
2130					if (BX.browser.IsChrome() || BX.browser.IsSafari() || BX.browser.IsIE10())
2131					{
2132						_this.editor.selection.ScrollIntoView();
2133					}
2134				},
2135
2136				state: BX.DoNothing,
2137				value: BX.DoNothing
2138			};
2139		},
2140
2141		GetInsertTable: function()
2142		{
2143			var
2144				_this = this,
2145				ATTRIBUTES = ['title', 'id', 'border', 'cellSpacing', 'cellPadding', 'align'];
2146
2147			function replaceTdByTh(td)
2148			{
2149				var
2150				//i, attribute,
2151					th = _this.document.createElement('TH');
2152
2153				while (td.firstChild)
2154					th.appendChild(td.firstChild);
2155
2156				//for (i = 0; i < td.attributes.length; i++)
2157				//{
2158				//	attribute = td.attributes[i];
2159				//	th.setAttribute(td.getAttribute());
2160				//}
2161				_this.editor.util.ReplaceNode(td, th);
2162			}
2163
2164			function replaceThByTd(th)
2165			{
2166				var
2167					td = _this.document.createElement('TD');
2168
2169				while (th.firstChild)
2170					td.appendChild(th.firstChild);
2171
2172				_this.editor.util.ReplaceNode(th, td);
2173			}
2174
2175			function applyAttributes(table, params)
2176			{
2177				var attr;
2178				table.removeAttribute("class");
2179
2180				for (attr in params)
2181				{
2182					if (params.hasOwnProperty(attr) && BX.util.in_array(attr, ATTRIBUTES))
2183					{
2184						if (params[attr] === '' || params[attr] == undefined)
2185						{
2186							table.removeAttribute(attr);
2187						}
2188						else
2189						{
2190							table.setAttribute(attr, params[attr]);
2191						}
2192					}
2193				}
2194				if (params.className)
2195				{
2196					table.className = params.className;
2197				}
2198
2199				table.removeAttribute("data-bx-no-border");
2200				if (table.getAttribute("border") == 0 || !table.getAttribute("border"))
2201				{
2202					table.removeAttribute("border");
2203					table.setAttribute("data-bx-no-border", "Y");
2204				}
2205
2206				if (params.width)
2207				{
2208					if (parseInt(params.width) == params.width)
2209					{
2210						params.width = params.width + 'px';
2211					}
2212
2213					if (table.getAttribute("width"))
2214					{
2215						table.setAttribute("width", params.width);
2216					}
2217					else
2218					{
2219						table.style.width = params.width;
2220					}
2221				}
2222
2223				if (params.height)
2224				{
2225					if (parseInt(params.height) == params.height)
2226					{
2227						params.height = params.height + 'px';
2228					}
2229
2230					if (table.getAttribute("height"))
2231					{
2232						table.setAttribute("height", params.height);
2233					}
2234					else
2235					{
2236						table.style.height = params.height;
2237					}
2238				}
2239
2240				var r, c, pCell;
2241				for(r = 0; r < table.rows.length; r++)
2242				{
2243					for(c = 0; c < table.rows[r].cells.length; c++)
2244					{
2245						pCell = table.rows[r].cells[c];
2246						if (
2247							((params.headers == 'top' || params.headers == 'topleft') && r == 0)
2248								||
2249								((params.headers == 'left' || params.headers == 'topleft') && c == 0)
2250							)
2251						{
2252							replaceTdByTh(pCell);
2253						}
2254						else if (pCell.nodeName == 'TH')
2255						{
2256							replaceThByTd(pCell);
2257						}
2258					}
2259				}
2260
2261				var pCaption = BX.findChild(table, {tag: 'CAPTION'}, false);
2262				if (params.caption)
2263				{
2264					if (pCaption)
2265					{
2266						pCaption.innerHTML = BX.util.htmlspecialchars(params.caption);
2267					}
2268					else
2269					{
2270						pCaption = _this.document.createElement('CAPTION');
2271						pCaption.innerHTML = BX.util.htmlspecialchars(params.caption);
2272						table.insertBefore(pCaption, table.firstChild);
2273					}
2274				}
2275				else if (pCaption)
2276				{
2277					BX.remove(pCaption);
2278				}
2279			}
2280
2281			return {
2282				exec: function(action, params)
2283				{
2284					// Iframe
2285					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
2286					{
2287						if (!params || !params.rows || !params.cols)
2288						{
2289							return false;
2290						}
2291
2292						_this.editor.iframeView.Focus();
2293						var table = params.table || _this.actions.insertTable.state(action, params);
2294
2295						params.rows = parseInt(params.rows) || 1;
2296						params.cols = parseInt(params.cols) || 1;
2297
2298						if (params.align == 'left' || _this.editor.bbCode)
2299						{
2300							params.align = '';
2301						}
2302
2303						if (!table)
2304						{
2305							table = _this.document.createElement('TABLE');
2306
2307							var
2308								tbody = table.appendChild(_this.document.createElement('TBODY')),
2309								r, c, row, cell;
2310
2311							params.rows = parseInt(params.rows) || 1;
2312							params.cols = parseInt(params.cols) || 1;
2313
2314							for(r = 0; r < params.rows; r++)
2315							{
2316								row = tbody.insertRow(-1);
2317								for(c = 0; c < params.cols; c++)
2318								{
2319									cell = BX.adjust(row.insertCell(-1), {html: '&nbsp;'});
2320								}
2321							}
2322							applyAttributes(table, params);
2323							_this.editor.selection.InsertNode(table);
2324
2325							var nextNode = _this.editor.util.GetNextNotEmptySibling(table);
2326							if (!nextNode)
2327							{
2328								_this.editor.util.InsertAfter(BX.create('BR', {}, _this.document), table);
2329							}
2330
2331							if (nextNode && nextNode.nodeName == 'BR' && !nextNode.nextSibling)
2332							{
2333								_this.editor.util.InsertAfter(_this.editor.util.GetInvisibleTextNode(), nextNode);
2334							}
2335						}
2336						else
2337						{
2338							applyAttributes(table, params);
2339						}
2340
2341						var nodeToSetCarret = table.rows[0].cells[0].firstChild;
2342						if (nodeToSetCarret)
2343						{
2344							_this.editor.selection.SetAfter(nodeToSetCarret);
2345						}
2346
2347						// For Firefox refresh white markers
2348						setTimeout(function(){_this.editor.util.Refresh(table);}, 10);
2349					}
2350					else // bbcode + textarea
2351					{
2352						_this.editor.textareaView.Focus();
2353						var
2354							tbl = '',
2355							i, j,
2356							cellHTML = _this.editor.INVISIBLE_SPACE;
2357
2358						if (params.rows > 0 && params.cols > 0)
2359						{
2360							tbl += "[TABLE]\n";
2361							for(i = 0; i < params.rows; i++)
2362							{
2363								tbl += "\t[TR]\n";
2364								for(j = 0; j < params.cols; j++)
2365								{
2366									tbl += "\t\t[TD]" + cellHTML + "[/TD]\n";
2367								}
2368								tbl += "\t[/TR]\n";
2369							}
2370							tbl += "[/TABLE]\n";
2371						}
2372
2373						_this.editor.textareaView.WrapWith(false, false, tbl);
2374					}
2375				},
2376
2377				state: function(action, value)
2378				{
2379					var
2380						selectedNode,
2381						selectedTable;
2382
2383					if (!_this.editor.util.DocumentHasTag(_this.document, 'TABLE'))
2384					{
2385						return false;
2386					}
2387
2388					selectedNode = _this.editor.selection.GetSelectedNode();
2389					if (!selectedNode)
2390					{
2391						return false;
2392					}
2393
2394					if (selectedNode.nodeName === 'TABLE')
2395					{
2396						return selectedNode;
2397					}
2398
2399					if (selectedNode.nodeType !== 1 /* element node */)
2400					{
2401						return false;
2402					}
2403
2404					selectedTable = _this.editor.selection.GetNodes(1, function(node)
2405					{
2406						return node.nodeName === "TABLE";
2407					});
2408
2409					// Works only for one table
2410					if (selectedTable.length !== 1)
2411					{
2412						return false;
2413					}
2414
2415					return selectedTable[0];
2416				},
2417				value: BX.DoNothing
2418			};
2419		},
2420
2421		//
2422		GetInsertList: function(params)
2423		{
2424			var _this = this;
2425			var
2426				bOrdered = !!params.bOrdered,
2427				listTag = bOrdered ? 'OL' : 'UL',
2428				otherListTag = bOrdered ? 'UL' : 'OL';
2429
2430			function getNextNotEmptySibling(node)
2431			{
2432				var nextSibling = node.nextSibling;
2433				while (nextSibling && nextSibling.nodeType == 3 && _this.editor.util.IsEmptyNode(nextSibling, true))
2434				{
2435					nextSibling = nextSibling.nextSibling;
2436				}
2437				return nextSibling;
2438			}
2439
2440			function removeList(list)
2441			{
2442				if (list.nodeName !== "MENU" && list.nodeName !== "UL" && list.nodeName !== "OL")
2443				{
2444					return;
2445				}
2446
2447				var
2448					frag = _this.document.createDocumentFragment(),
2449					previousSibling = list.previousSibling,
2450					firstChild,
2451					lastChild,
2452					bAppendBr,
2453					listItem;
2454
2455				if (previousSibling && !_this.editor.util.IsBlockElement(previousSibling) && !_this.editor.util.IsEmptyNode(previousSibling, true))
2456				{
2457					frag.appendChild(_this.document.createElement("BR"));
2458				}
2459
2460				while (listItem = list.firstChild)
2461				{
2462					lastChild = listItem.lastChild;
2463					while (firstChild = listItem.firstChild)
2464					{
2465						// Custom bullit items
2466						if (firstChild.nodeName == 'I' && firstChild.innerHTML == '' && firstChild.className != '')
2467						{
2468							BX.remove(firstChild);
2469							firstChild = listItem.firstChild;
2470							if (!firstChild)
2471								break;
2472						}
2473
2474						bAppendBr = firstChild === lastChild &&
2475							!_this.editor.util.IsBlockElement(firstChild) &&
2476							firstChild.nodeName !== "BR";
2477
2478						frag.appendChild(firstChild);
2479						if (bAppendBr)
2480						{
2481							frag.appendChild(_this.document.createElement("BR"));
2482						}
2483					}
2484					listItem.parentNode.removeChild(listItem);
2485				}
2486
2487				var nextSibling = _this.editor.util.GetNextNotEmptySibling(list);
2488				if (nextSibling && nextSibling.nodeName == 'BR' && frag.lastChild && frag.lastChild.nodeName == 'BR')
2489				{
2490					// Remove unnecessary BR
2491					BX.remove(frag.lastChild);
2492				}
2493
2494				list.parentNode.replaceChild(frag, list);
2495			}
2496
2497			function convertToList(element, listType)
2498			{
2499				if (!element || !element.parentNode)
2500					return false;
2501
2502				var nodeName = element.nodeName.toUpperCase();
2503
2504				if (nodeName === "UL" || nodeName === "OL" || nodeName === "MENU")
2505				{
2506					return element;
2507				}
2508
2509				var
2510					list = _this.document.createElement(listType),
2511					childNode,
2512					currentLi;
2513
2514				while (element.firstChild)
2515				{
2516					currentLi = currentLi || list.appendChild(_this.document.createElement("li"));
2517					childNode = element.firstChild;
2518
2519					if (_this.editor.util.IsBlockElement(childNode))
2520					{
2521						currentLi = currentLi.firstChild ? list.appendChild(_this.document.createElement("li")) : currentLi;
2522						currentLi.appendChild(childNode);
2523						currentLi = null;
2524						continue;
2525					}
2526
2527					if (childNode.nodeName === "BR")
2528					{
2529						currentLi = currentLi.firstChild ? null : currentLi;
2530						element.removeChild(childNode);
2531						continue;
2532					}
2533
2534					currentLi.appendChild(childNode);
2535				}
2536
2537				element.parentNode.replaceChild(list, element);
2538				return list;
2539			}
2540
2541			function isListNode(n)
2542			{
2543				return n.nodeName == 'OL' || n.nodeName == 'UL' || n.nodeName == 'MENU'
2544			}
2545
2546			function getSelectedList(tag, node)
2547			{
2548				if (!node)
2549				{
2550					node = _this.editor.selection.GetSelectedNode();
2551				}
2552
2553				if (!node || node.nodeName == 'BODY')
2554				{
2555					var
2556						range = _this.editor.selection.GetRange(),
2557						commonAncestor = _this.editor.selection.GetCommonAncestorForRange(range);
2558
2559					if (commonAncestor && isListNode(commonAncestor))
2560					{
2561						node = commonAncestor;
2562					}
2563					else
2564					{
2565						var
2566							i, onlyList = true, list, parentList,
2567							nodes = range.getNodes([1]),
2568							l = nodes.length;
2569
2570						if (commonAncestor)
2571						{
2572							list = parentList = BX.findParent(commonAncestor, isListNode, _this.document.body);
2573						}
2574
2575						if (!parentList)
2576						{
2577							for (i = 0; i < l; i++)
2578							{
2579								parentList = BX.findParent(nodes[i], isListNode, commonAncestor);
2580								if (!parentList || (list && parentList != list))
2581								{
2582									onlyList = false;
2583									break;
2584								}
2585								list = parentList;
2586							}
2587						}
2588
2589						if (!list)
2590						{
2591							var
2592								_list = false;
2593							for (i = 0; i < l; i++)
2594							{
2595								if (isListNode(nodes[i]))
2596								{
2597									_list = nodes[i];
2598									break;
2599								}
2600							}
2601
2602							if (_list)
2603							{
2604								var ok = true;
2605								for (i = 0; i < l; i++)
2606								{
2607									if (nodes[i] == _list ||
2608										BX.findParent(nodes[i], function(n){return n === _list;}, _this.document.body) ||
2609										nodes[i].nodeName == 'BR' ||
2610										_this.editor.util.IsEmptyNode(nodes[i])
2611										)
2612									{
2613									}
2614									else
2615									{
2616										ok = false;
2617										break;
2618									}
2619								}
2620
2621								if (ok)
2622								{
2623									list = _list;
2624								}
2625							}
2626						}
2627
2628						if (list)
2629						{
2630							node = list;
2631						}
2632					}
2633				}
2634
2635				return node && node.nodeName == tag ? node : BX.findParent(node, {tagName: tag}, _this.document.body);
2636			}
2637
2638
2639			function customBullit(list, bullitParams)
2640			{
2641				if (!list)
2642					return false;
2643
2644				if (!bullitParams)
2645					bullitParams = {tag: 'I', remove: true};
2646
2647				var i, node, fch, doc = list.ownerDocument;
2648				for (i = 0; i < list.childNodes.length; i++)
2649				{
2650					node = list.childNodes[i];
2651					if (node && node.nodeType == 1 && node.nodeName == 'LI')
2652					{
2653						fch = node.firstChild;
2654						// Custom bullit already here
2655						if (fch.nodeName == bullitParams.tag && fch.innerHTML == '')
2656						{
2657							if (bullitParams.remove)
2658								BX.remove(fch);
2659							else
2660								fch.className = bullitParams.className;
2661						}
2662						else // Add custom bullits to the list
2663						{
2664							node.insertBefore(BX.create(bullitParams.tag, {props: {className: bullitParams.className}}, doc), fch);
2665						}
2666					}
2667				}
2668			}
2669
2670			function getCustomBullitClass(list)
2671			{
2672				if (!list)
2673					return false;
2674
2675				var i, node, fch;
2676				for (i = 0; i < list.childNodes.length; i++)
2677				{
2678					node = list.childNodes[i];
2679					if (node && node.nodeType == 1 && node.nodeName == 'LI')
2680					{
2681						fch = node.firstChild;
2682						if (fch && fch.nodeName == 'I' && fch.innerHTML == '' && fch.className !== '')
2683						{
2684							return fch.className;
2685						}
2686					}
2687				}
2688
2689				return false;
2690			}
2691
2692			function checkCustomBullitList(list, bullitClass, setFocusAfterLastBullit)
2693			{
2694				if (list && (list.nodeName == 'UL' || list.nodeName == 'OL'))
2695				{
2696					var i, node, fch, doc = list.ownerDocument, lastBullit;
2697					for (i = 0; i < list.childNodes.length; i++)
2698					{
2699						node = list.childNodes[i];
2700						if (node && node.nodeType == 1 && node.nodeName == 'LI' && node.firstChild)
2701						{
2702							fch = node.firstChild;
2703							if (fch.nodeName == 'I' && fch.innerHTML == '' && fch.className !== '')
2704							{
2705								if (!bullitClass)
2706									bullitClass = fch.className;
2707							}
2708							else if (fch.nodeName == 'I' && fch.innerHTML == '' && bullitClass)
2709							{
2710								fch.className = bullitClass;
2711								lastBullit = fch;
2712							}
2713							else if(fch && bullitClass)
2714							{
2715								lastBullit = node.insertBefore(BX.create('I', {props: {className: bullitClass}}, doc), fch);
2716							}
2717						}
2718					}
2719
2720					if (setFocusAfterLastBullit && lastBullit)
2721						_this.editor.selection._MoveCursorAfterNode(lastBullit);
2722				}
2723			}
2724
2725			return {
2726				exec: function(action, params)
2727				{
2728					// Iframe
2729					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
2730					{
2731						var range = _this.editor.selection.GetRange();
2732
2733						if (_this.IsSupportedByBrowser(action) && range.collapsed)
2734						{
2735							_this.document.execCommand(action, false, null);
2736						}
2737						else
2738						{
2739							var
2740								selectedNode = _this.editor.selection.GetSelectedNode(),
2741								list = getSelectedList(listTag, selectedNode),
2742								otherList = getSelectedList(otherListTag, selectedNode),
2743								isEmpty,
2744								tempElement;
2745
2746							if (list)
2747							{
2748								_this.editor.selection.ExecuteAndRestoreSimple(function()
2749								{
2750									removeList(list);
2751								});
2752							}
2753							else if (otherList)
2754							{
2755								_this.editor.selection.ExecuteAndRestoreSimple(function()
2756								{
2757									_this.editor.util.RenameNode(otherList, listTag);
2758								});
2759							}
2760							else
2761							{
2762								tempElement = _this.document.createElement("span");
2763								_this.editor.selection.Surround(tempElement);
2764								isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === _this.editor.INVISIBLE_SPACE;
2765								_this.editor.selection.ExecuteAndRestoreSimple(function()
2766								{
2767									// mantis #54087
2768									var i, spans = tempElement.getElementsByTagName('SPAN');
2769									for (i = spans.length - 1; i >= 0; i--)
2770									{
2771										// Clean spans without classes styles and id's
2772										if (!spans[i].className && !spans[i].id && !spans[i].style.cssText)
2773										{
2774											_this.editor.util.ReplaceWithOwnChildren(spans[i]);
2775										}
2776									}
2777									list = convertToList(tempElement, listTag);
2778								});
2779
2780								if (list)
2781								{
2782									var i = 0, item;
2783									while (i < list.childNodes.length)
2784									{
2785										item = list.childNodes[i];
2786										if (item.nodeName == 'LI')
2787										{
2788											if (_this.editor.util.IsEmptyNode(item, true, true))
2789											{
2790												BX.remove(item);
2791												continue;
2792											}
2793											i++;
2794										}
2795										else if (item.nodeType == 1)
2796										{
2797											BX.remove(item);
2798										}
2799									}
2800
2801									// Mantis: #53646, #53820
2802									var prevSib = _this.editor.util.GetPreviousNotEmptySibling(list);
2803									if (prevSib && (
2804										prevSib.nodeName == 'BLOCKQUOTE' ||
2805										prevSib.nodeName == 'PRE' ||
2806										prevSib.nodeName == 'UL' ||
2807										prevSib.nodeName == 'OL'
2808										)
2809										&& list.childNodes[0] && BX.findChild(list.childNodes[0], {tag: prevSib.nodeName}))
2810									{
2811										if (BX.util.trim(_this.editor.util.GetTextContent(prevSib)) == '')
2812										{
2813											BX.remove(prevSib);
2814										}
2815									}
2816								}
2817
2818								if (isEmpty && list && list.querySelector)
2819								{
2820									_this.editor.selection.SelectNode(list.querySelector("li"));
2821								}
2822							}
2823						}
2824					}
2825					else // bbcode + textarea
2826					{
2827						if (params && params.items)
2828						{
2829							_this.editor.textareaView.Focus();
2830							var lst = '[LIST' + (bOrdered ? '=1' : '')+ ']\n', it;
2831
2832							for(it = 0; it < params.items.length; it++)
2833							{
2834								lst += "\t[*]" + params.items[it] + "\n";
2835							}
2836							lst += "[/LIST]\n";
2837
2838							_this.editor.textareaView.WrapWith(false, false, lst);
2839						}
2840					}
2841				},
2842
2843				state: function()
2844				{
2845					return getSelectedList(listTag) || false;
2846				},
2847
2848				value: BX.DoNothing,
2849
2850				customBullit: customBullit,
2851				getCustomBullitClass: getCustomBullitClass,
2852				checkCustomBullitList: checkCustomBullitList
2853			};
2854		},
2855
2856		GetAlign: function()
2857		{
2858			var
2859				CLASS_NAME_TMP = 'bx-align-tmp',
2860				LIST_ALIGN_ATTR = 'data-bx-checked-align-list',
2861				DEFAULT_VALUE = 'left',
2862				TABLE_NODES = {TD: 1, TR: 1, TH: 1, TABLE: 1, TBODY: 1, CAPTION: 1, COL: 1, COLGROUP: 1, TFOOT: 1, THEAD: 1},
2863				ALIGN_NODES = {IMG: 1, P: 1, DIV: 1, TABLE: 1, H1: 1, H2: 1, H3: 1, H4: 1, H5: 1, H6: 1},
2864				_this = this;
2865
2866			function checkNodeAlign(n)
2867			{
2868				var
2869					nodeName = n.nodeName, align,
2870					res = false;
2871
2872				if (n.nodeType === 1)
2873				{
2874					align = n.style.textAlign;
2875					if (n.style.textAlign)
2876					{
2877						res = {node: n, style: align};
2878					}
2879
2880					if (ALIGN_NODES[nodeName])
2881					{
2882						align = n.getAttribute('align');
2883						if (align)
2884						{
2885							if (res)
2886							{
2887								res.attribute = align;
2888							}
2889							else
2890							{
2891								res = {node: n, attribute: align};
2892							}
2893						}
2894					}
2895				}
2896				return res;
2897			}
2898
2899			function isCell(n)
2900			{
2901				return n && (n.nodeName == 'TD' || n.nodeName == 'TH');
2902			}
2903
2904			function alignTableNode(n, value)
2905			{
2906				n.setAttribute('data-bx-tmp-align', value);
2907				n.style.textAlign = value;
2908			}
2909
2910			function alignTable(n, value)
2911			{
2912				if (value == 'left' || value == 'justify' || _this.editor.bbCode)
2913				{
2914					n.removeAttribute('align');
2915				}
2916				else
2917				{
2918					n.setAttribute('align', value);
2919				}
2920			}
2921
2922			function checkAlignTable(table, value)
2923			{
2924				var
2925					ths,
2926					i, res = true,
2927					tds = table.getElementsByTagName("TD");
2928
2929				for (i = 0; i < tds.length; i++)
2930				{
2931					if (tds[i].getAttribute('data-bx-tmp-align') != value)
2932					{
2933						res = false;
2934						break;
2935					}
2936				}
2937
2938				if (res)
2939				{
2940					ths = table.getElementsByTagName("TH");
2941					for (i = 0; i < ths.length; i++)
2942					{
2943						if (ths[i].getAttribute('data-bx-tmp-align') != value)
2944						{
2945							res = false;
2946							break;
2947						}
2948					}
2949				}
2950
2951				if (res)
2952				{
2953					alignTable(table, value);
2954				}
2955			}
2956
2957			function createAlignNodeInside(node, value)
2958			{
2959				var alignNode = BX.create("DIV", {style: {textAlign: value}, html: node.innerHTML}, _this.editor.GetIframeDoc());
2960				node.innerHTML = '';
2961				node.appendChild(alignNode);
2962				return alignNode;
2963			}
2964
2965			function createAlignNodeOutside(node, value)
2966			{
2967				var alignNode = BX.create("DIV", {style: {textAlign: value}}, _this.editor.GetIframeDoc());
2968				node.parentNode.insertBefore(alignNode, node);
2969				alignNode.appendChild(node);
2970				return alignNode;
2971			}
2972
2973			function checkListItemsAlign(list, item, value)
2974			{
2975				var
2976					doc = _this.editor.GetIframeDoc(),
2977					bb = _this.editor.bbCode;
2978				if (!list && item)
2979				{
2980					list = BX.findParent(item, function(n)
2981					{
2982						return n.nodeName == 'OL' || n.nodeName == 'UL' || n.nodeName == 'MENU';
2983					}, doc);
2984				}
2985
2986				if (list && !list.getAttribute(LIST_ALIGN_ATTR))
2987				{
2988					var
2989						i, clean = true,
2990						lis = list.getElementsByTagName('LI');
2991
2992					for (i = 0; i < lis.length; i++)
2993					{
2994						if (lis[i].style.textAlign !== value)
2995						{
2996							clean = false;
2997							break;
2998						}
2999					}
3000
3001					if (bb)
3002					{
3003						list.style.textAlign = '';
3004						if (list.style.cssText == '')
3005						{
3006							list.removeAttribute('style');
3007						}
3008						cleanListItemsAlign(list);
3009
3010						createAlignNodeOutside(list, value);
3011					}
3012					else if (clean)
3013					{
3014						list.style.textAlign = value;
3015						cleanListItemsAlign(list);
3016					}
3017
3018					list.setAttribute(LIST_ALIGN_ATTR, 'Y');
3019					return list;
3020				}
3021
3022				return false;
3023			}
3024
3025			function cleanListItemsAlign(list)
3026			{
3027				var i, lis = list.getElementsByTagName('LI');
3028				for (i = 0; i < lis.length; i++)
3029				{
3030					lis[i].style.textAlign = '';
3031					if (lis[i].style.cssText == '')
3032					{
3033						lis[i].removeAttribute('style');
3034					}
3035				}
3036			}
3037
3038			function pushTable(arr, newTable)
3039			{
3040				if (newTable && newTable.nodeName == 'TABLE')
3041				{
3042					var i, found = false;
3043					for (i = 0; i < arr.length; i++)
3044					{
3045						if (arr[i] == newTable)
3046						{
3047							found = true;
3048							break;
3049						}
3050					}
3051					if (!found)
3052					{
3053						arr.push(newTable);
3054					}
3055				}
3056				return arr;
3057			}
3058
3059			return {
3060				exec: function(action, value)
3061				{
3062					var res;
3063
3064					// Iframe
3065					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
3066					{
3067						var
3068							i,
3069							tagName = 'P',
3070							range = _this.editor.selection.GetRange(),
3071							blockElement = false,
3072							tableElement = false,
3073							listNode = false,
3074							bookmark = _this.editor.selection.GetBookmark(),
3075							selectedNode = _this.editor.selection.GetSelectedNode();
3076
3077						if (selectedNode)
3078						{
3079							if (_this.editor.util.IsBlockNode(selectedNode))
3080							{
3081								blockElement = selectedNode;
3082							}
3083							else if (selectedNode.nodeType == 1 && TABLE_NODES[selectedNode.nodeName])
3084							{
3085								tableElement = selectedNode;
3086								res = true;
3087								setTimeout(function(){
3088									_this.editor.selection.SelectNode(tableElement);
3089									if (tableElement.nodeName == 'TABLE')
3090									{
3091										alignTable(tableElement, value);
3092									}
3093								}, 10);
3094							}
3095							else
3096							{
3097								if (selectedNode.nodeName == 'LI')
3098								{
3099									listNode = selectedNode;
3100								}
3101								else if (selectedNode.nodeName == 'OL' || selectedNode.nodeName == 'UL' || selectedNode.nodeName == 'MENU')
3102								{
3103									if (_this.editor.bbCode)
3104									{
3105										createAlignNodeOutside(selectedNode, value);
3106										selectedNode.style.textAlign = '';
3107									}
3108									else
3109									{
3110										selectedNode.style.textAlign = value;
3111									}
3112									res = true;
3113									cleanListItemsAlign(selectedNode);
3114									setTimeout(function(){_this.editor.selection.SelectNode(selectedNode);}, 10);
3115								}
3116								else
3117								{
3118									listNode = BX.findParent(selectedNode, function(n)
3119									{
3120										return n.nodeName == 'LI';
3121									}, _this.document.body);
3122
3123								}
3124
3125								if (listNode)
3126								{
3127									if (_this.editor.bbCode)
3128									{
3129										createAlignNodeInside(listNode, value);
3130										listNode.style.textAlign = '';
3131									}
3132									else
3133									{
3134										listNode.style.textAlign = value;
3135									}
3136									res = true;
3137									setTimeout(function(){_this.editor.selection.SelectNode(listNode);}, 10);
3138								}
3139								else
3140								{
3141									blockElement = BX.findParent(selectedNode, function(n)
3142									{
3143										return _this.editor.util.IsBlockNode(n) && !_this.actions.quote.checkNode(n);
3144									}, _this.document.body);
3145								}
3146							}
3147						}
3148						else
3149						{
3150							// In Chrome when we select some parts of table we apply align for tds, but if entire table was selected - we trying to continue align other elements.
3151							var
3152								tables = [],
3153								tableIsHere = false,
3154								arLists = [], arLis = [],
3155								nodes = range.getNodes([1]);
3156
3157							for (i = 0; i < nodes.length; i++)
3158							{
3159								if (isCell(nodes[i]))
3160								{
3161									tables = pushTable(tables, BX.findParent(nodes[i], {tagName: 'TABLE'}));
3162									alignTableNode(nodes[i], value);
3163									res = true;
3164								}
3165
3166								if (nodes[i].nodeName == 'TABLE')
3167								{
3168									tables = pushTable(tables, nodes[i]);
3169									tableIsHere = true;
3170								}
3171								else if (nodes[i].nodeName == 'OL' || nodes[i].nodeName == 'UL' || nodes[i].nodeName == 'MENU')
3172								{
3173									nodes[i].style.textAlign = value;
3174									arLists.push(nodes[i]);
3175									res = true;
3176								}
3177								else if (nodes[i].nodeName == 'LI')
3178								{
3179									nodes[i].style.textAlign = value;
3180									res = true;
3181									arLis.push(nodes[i]);
3182								}
3183							}
3184
3185							for (i = 0; i < tables.length; i++)
3186							{
3187								checkAlignTable(tables[i], value);
3188							}
3189
3190							// Example: ctra+a was pressed
3191							if (res)
3192							{
3193								var commonAncestor = _this.editor.selection.GetCommonAncestorForRange(range);
3194								if (commonAncestor && commonAncestor.nodeName == 'BODY')
3195								{
3196									res = false;
3197								}
3198							}
3199
3200							for (i = 0; i < arLists.length; i++)
3201							{
3202								cleanListItemsAlign(arLists[i]);
3203							}
3204							var arCheckedLists = [], checkedList;
3205
3206							for (i = 0; i < arLis.length; i++)
3207							{
3208								checkedList = checkListItemsAlign(false, arLis[i], value);
3209								if (checkedList)
3210								{
3211									arCheckedLists.push(checkedList);
3212								}
3213							}
3214							for (i = 0; i < arCheckedLists.length; i++)
3215							{
3216								arCheckedLists[i].removeAttribute(LIST_ALIGN_ATTR);
3217							}
3218						}
3219
3220						if (!res)
3221						{
3222							// Simple situation - we inside of the block element - just add text-align to it...
3223							if (blockElement)
3224							{
3225								if (_this.editor.bbCode)
3226								{
3227									createAlignNodeInside(blockElement, value);
3228									blockElement.style.textAlign = '';
3229								}
3230								else
3231								{
3232									// Accept all block tags except DIVs
3233									tagName = blockElement.tagName != 'DIV' ? blockElement.tagName : 'P';
3234									res = _this.actions.formatBlock.exec('formatBlock', tagName, null, {textAlign: value});
3235								}
3236								_this.editor.util.Refresh(blockElement);
3237							}
3238							else if(tableElement)
3239							{
3240								if (isCell(tableElement))
3241								{
3242									alignTableNode(tableElement, value);
3243								}
3244								else
3245								{
3246									var
3247										tds = BX.findChild(tableElement, isCell, true, true),
3248										ths = BX.findChild(tableElement, isCell, true, true);
3249
3250									for (i = 0; i < tds.length; i++)
3251									{
3252										alignTableNode(tds[i], value);
3253									}
3254									for (i = 0; i < ths.length; i++)
3255									{
3256										alignTableNode(ths[i], value);
3257									}
3258								}
3259							}
3260							else if (range.collapsed)
3261							{
3262								res = _this.actions.formatBlock.exec('formatBlock', 'P', CLASS_NAME_TMP, {textAlign: value});
3263
3264								// Selection workaround mantis:53937
3265								var
3266									focusNode,
3267									alignNodes = _this.document.querySelectorAll("." + CLASS_NAME_TMP);
3268
3269								for (i = 0; i <= alignNodes.length; i++)
3270								{
3271									BX.removeClass(alignNodes[i], CLASS_NAME_TMP);
3272									if (i == 0)
3273									{
3274										focusNode = alignNodes[i].firstNode;
3275										if (!focusNode)
3276											focusNode = alignNodes[i].appendChild(_this.editor.util.GetInvisibleTextNode());
3277										setTimeout(function()
3278										{
3279											if (focusNode)
3280												_this.editor.selection.SetAfter(focusNode);
3281										}, 100);
3282									}
3283								}
3284							}
3285							else
3286							{
3287								// Image selected
3288								var image = _this.actions.insertImage.state();
3289
3290								if (!res && false)
3291								{
3292									var onlyPar = true;
3293									nodes = range.getNodes([1]);
3294									if (nodes && nodes.length > 0)
3295									{
3296										for (i = 0; i < nodes.length; i++)
3297										{
3298											if (nodes[i].nodeName == "P")
3299											{
3300												nodes[i].style.textAlign = value;
3301											}
3302											else
3303											{
3304												onlyPar = false;
3305											}
3306										}
3307										res = onlyPar;
3308									}
3309								}
3310
3311								// Mixed content
3312								if (!res)
3313								{
3314									tagName = _this.editor.bbCode ? 'DIV' : 'P';
3315
3316									res = _this.actions.formatBlock.exec('formatBlock', tagName, null, {textAlign: value}, {leaveChilds: true, splitBlock: true});
3317									if (res && typeof res == 'object' && res.nodeName == tagName)
3318									{
3319										var
3320											iter = 0, maxIter = 2000, prev,
3321											child, newPar, createNewPar = false;
3322
3323										// mantis:#54026
3324										if (res.firstChild && res.firstChild.nodeName == 'BLOCKQUOTE')
3325										{
3326											prev = _this.editor.util.GetPreviousNotEmptySibling(res);
3327											if (prev && prev.nodeName == 'BLOCKQUOTE' && _this.editor.util.IsEmptyNode(prev))
3328											{
3329												BX.remove(prev);
3330											}
3331										}
3332
3333										i = 0;
3334
3335										while (i < res.childNodes.length || iter > maxIter)
3336										{
3337											child = res.childNodes[i];
3338											if(_this.editor.util.IsBlockNode(child))
3339											{
3340												child.style.textAlign = value;
3341												createNewPar = true;
3342												i++;
3343											}
3344											else
3345											{
3346												if (!newPar || createNewPar)
3347												{
3348													newPar = _this.document.createElement(tagName);
3349													newPar.style.textAlign = value;
3350													res.insertBefore(newPar, child);
3351													i++;
3352												}
3353
3354												newPar.appendChild(child);
3355												createNewPar = false;
3356											}
3357											iter++;
3358										}
3359
3360										// Clean useless <p></p> before and after
3361										if (res.previousSibling && res.previousSibling.nodeName == "P" && _this.editor.util.IsEmptyNode(res.previousSibling, true, true))
3362										{
3363											BX.remove(res.previousSibling);
3364										}
3365										if (res.nextSibling && res.nextSibling.nodeName == "P" && _this.editor.util.IsEmptyNode(res.nextSibling, true, true))
3366										{
3367											BX.remove(res.nextSibling);
3368										}
3369										_this.editor.util.ReplaceWithOwnChildren(res);
3370
3371										// Selection workaround mantis:53937
3372										setTimeout(function()
3373										{
3374											if (newPar)
3375												_this.editor.selection.SelectNode(newPar);
3376										}, 100);
3377									}
3378								}
3379
3380								if (image)
3381								{
3382									// For Firefox
3383									_this.editor.util.Refresh(image);
3384								}
3385							}
3386						}
3387
3388						setTimeout(function(){_this.editor.selection.SetBookmark(bookmark);}, 10);
3389					}
3390					else // bbcode + textarea
3391					{
3392						if (value)
3393						{
3394							res = _this.actions.formatBbCode.exec(action, {tag: value.toUpperCase()});
3395						}
3396					}
3397
3398					return res;
3399				},
3400				state: function(action, value)
3401				{
3402					var
3403						alignRes, node,
3404						selectedNode = _this.editor.selection.GetSelectedNode();
3405
3406					if (selectedNode)
3407					{
3408						alignRes = checkNodeAlign(selectedNode);
3409						if (!alignRes)
3410						{
3411							node = BX.findParent(selectedNode, function(n)
3412							{
3413								alignRes = checkNodeAlign(n);
3414								return alignRes;
3415							}, _this.document.body);
3416						}
3417
3418						return {
3419							node: alignRes ? alignRes.node : null,
3420							value: alignRes ? (alignRes.style || alignRes.attribute) : DEFAULT_VALUE,
3421							res: alignRes
3422						};
3423					}
3424					else
3425					{
3426						var
3427							result = {node: null, value: DEFAULT_VALUE, res: true},
3428							range = _this.editor.selection.GetRange();
3429						if (!range.collapsed)
3430						{
3431							var
3432								alRes,
3433								curValue = '', i,
3434								nodes = range.getNodes([1]);
3435
3436							for (i = 0; i < nodes.length; i++)
3437							{
3438								if (!_this.editor.util.CheckSurrogateNode(nodes[i]) &&
3439									nodes[i].nodeName !== 'BR' &&
3440									_this.editor.util.GetNodeDomOffset(nodes[i]) == 0
3441									)
3442								{
3443									alRes = checkNodeAlign(nodes[i]);
3444									value = alRes ? (alRes.style || alRes.attribute) : DEFAULT_VALUE;
3445
3446									if (!curValue)
3447									{
3448										curValue = value;
3449									}
3450
3451									if (value != curValue)
3452									{
3453										result.res = false;
3454										break;
3455									}
3456
3457								}
3458							}
3459							if (result.res)
3460							{
3461								result.value = curValue;
3462							}
3463						}
3464						else
3465						{
3466							result.res = false;
3467						}
3468
3469						return result;
3470					}
3471				},
3472				value: BX.DoNothing
3473			};
3474		},
3475
3476		GetIndent: function()
3477		{
3478			var _this = this;
3479			return {
3480				exec: function(action)
3481				{
3482					// Mantis bug workaround: #59811
3483					var rng = _this.editor.selection.GetRange();
3484					if (rng && rng.collapsed && rng.endContainer == rng.startContainer && BX.util.in_array(rng.startContainer.nodeName, ['TD', 'TR', 'TH']) && arguments[1] !== rng.startContainer.nodeName)
3485					{
3486						var
3487							id = 'bxed_bogus_node_59811',
3488							focusNode = rng.startContainer.appendChild(BX.create('SPAN', {props: {id: id}, html: '&nbsp;'}, _this.document));is_text = _this.editor.util.GetInvisibleTextNode();
3489
3490						if (focusNode)
3491						{
3492							rng.setStartBefore(focusNode);
3493							rng.setEndAfter(focusNode);
3494							_this.editor.selection.SetSelection(rng);
3495
3496							return setTimeout(function()
3497							{
3498								_this.actions.indent.exec(action, rng.startContainer.nodeName);
3499
3500								var focusNode = _this.editor.GetIframeElement(id);
3501								if (focusNode)
3502								{
3503									_this.editor.selection.SetAfter(focusNode);
3504									BX.remove(focusNode);
3505								}
3506
3507							}, 0);
3508						}
3509					}
3510
3511					if (_this.IsSupportedByBrowser(action))
3512					{
3513						_this.document.execCommand(action);
3514					}
3515					else
3516					{
3517						_this.actions.formatBlock.exec('formatBlock', 'BLOCKQUOTE');
3518					}
3519
3520					var range = _this.editor.selection.GetRange();
3521					if (range)
3522					{
3523						var bqCnt = 0, i, nodes = range.getNodes([1]);
3524
3525						for (i = 0; i < nodes.length; i++)
3526						{
3527							if (nodes[i].nodeName == 'BLOCKQUOTE')
3528							{
3529								bqCnt++;
3530								nodes[i].removeAttribute('style');
3531							}
3532						}
3533
3534						if (range.startContainer)
3535						{
3536							var
3537								invis_text,
3538								bq = false,
3539								node = range.startContainer;
3540							while (node)
3541							{
3542								node = BX.findParent(node, {tag: 'BLOCKQUOTE'}, _this.document.body);
3543								if (node)
3544								{
3545									if (!bq)
3546										bq = node;
3547									node.removeAttribute('style');
3548								}
3549							}
3550
3551							if (bq)
3552							{
3553								invis_text = _this.editor.util.GetInvisibleTextNode();
3554								bq.appendChild(invis_text);
3555								_this.editor.selection.SetAfter(invis_text);
3556							}
3557						}
3558					}
3559				},
3560				state: function(action, value)
3561				{
3562					var
3563						res = false,
3564						range = _this.editor.selection.GetRange();
3565					if (range)
3566					{
3567						var commonAncestor = _this.editor.selection.GetCommonAncestorForRange(range);
3568						if (commonAncestor && commonAncestor.nodeType == 1 && commonAncestor.nodeName === 'BLOCKQUOTE')
3569						{
3570							res = commonAncestor;
3571						}
3572					}
3573					return res;
3574				},
3575				value: BX.DoNothing
3576			};
3577		},
3578
3579		GetOutdent: function()
3580		{
3581			var _this = this;
3582			return {
3583				exec: function(action, value)
3584				{
3585					var
3586						i,
3587						attr = 'data-bx-tmp-flag',
3588						doc = _this.editor.GetIframeDoc(),
3589						blockNodes = doc.getElementsByTagName('BLOCKQUOTE');
3590
3591					//if (blockNodes && blockNodes.length > 0)
3592					{
3593						var
3594							parNodesToClear = [],
3595							parNodes = doc.getElementsByTagName('P');
3596						for (i = 0; i < parNodes.length; i++)
3597						{
3598							parNodes[i].setAttribute(attr, 'Y');
3599						}
3600						_this.document.execCommand(action);
3601
3602						parNodes = doc.getElementsByTagName('P');
3603						for (i = 0; i < parNodes.length; i++)
3604						{
3605							if (!parNodes[i].getAttribute(attr))
3606							{
3607								parNodesToClear.push(parNodes[i]);
3608							}
3609							else
3610							{
3611								parNodes[i].removeAttribute(attr);
3612							}
3613						}
3614
3615						_this.editor.selection.ExecuteAndRestoreSimple(function()
3616						{
3617							for (i = 0; i < parNodesToClear.length; i++)
3618							{
3619								_this.actions.formatBlock.addBrBeforeAndAfter(parNodesToClear[i]);
3620								_this.editor.util.ReplaceWithOwnChildren(parNodesToClear[i]);
3621							}
3622						});
3623					}
3624				},
3625				state: function(action, value)
3626				{
3627					var
3628						range = _this.editor.selection.GetRange();
3629
3630					return false;
3631				},
3632				value: BX.DoNothing
3633			};
3634		},
3635
3636		GetFontFamily: function()
3637		{
3638			var _this = this;
3639			return {
3640				exec: function(action, value)
3641				{
3642					var res;
3643
3644					// Iframe
3645					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
3646					{
3647						if (value)
3648							res = _this.actions.formatInline.exec(action, value, "span", {fontFamily: value});
3649						else // Clear fontFamily format
3650							res = _this.actions.formatInline.exec(action, value, "span", {fontFamily: null}, null, {bClear: true});
3651					}
3652					else // textarea + bbcode
3653					{
3654						return _this.actions.formatBbCode.exec(action, {tag: 'FONT', value: value});
3655					}
3656
3657					return res;
3658				},
3659
3660				state: function(action, value)
3661				{
3662					return _this.actions.formatInline.state(action, value, "span", {fontFamily: value});
3663				},
3664
3665				value: BX.DoNothing
3666			};
3667		},
3668
3669		GetFormatStyle: function()
3670		{
3671			var
3672				_this = this,
3673				styleSel = this.editor.toolbar.controls.StyleSelector,
3674				classList = styleSel ? styleSel.checkedClasses : [],
3675				tagList = styleSel ? styleSel.checkedTags : [];
3676
3677			function isNodeSuitable(node)
3678			{
3679				if (node && node.nodeType == 1 && node.nodeName !== 'BODY' &&
3680					(
3681						BX.util.in_array(node.nodeName, tagList) ||
3682						BX.util.in_array(node.className, classList)
3683					))
3684				{
3685					return !_this.editor.GetBxTag(node.id).tag;
3686				}
3687				return false;
3688			}
3689
3690			return {
3691				exec: function(action, value)
3692				{
3693					if (!value) // Clear font style
3694					{
3695						return _this.actions.removeFormat.exec('removeFormat');
3696					}
3697					else if (typeof value === 'string') // Tag name - H1, H2
3698					{
3699						return _this.actions.formatBlock.exec('formatBlock', value);
3700					}
3701					else if (typeof value === 'object') // class name from template-s css
3702					{
3703						// Handle font awesome list
3704						if (value.tag == 'UL')
3705						{
3706							var list = _this.actions.insertUnorderedList.state();
3707							if (list && value.className && value.className.indexOf('~~') !== -1)
3708							{
3709								var cn = value.className.split('~~');
3710								if (cn && cn.length >=2 )
3711								{
3712									var
3713										listClass = cn[0],
3714										bullitClass = cn[1];
3715
3716									list.className = listClass;
3717									_this.actions.insertUnorderedList.customBullit(list, {tag: 'I', className: bullitClass, html: ''});
3718								}
3719							}
3720							else if (list)
3721							{
3722								list.className = value.className || '';
3723								_this.actions.insertUnorderedList.customBullit(list, false);
3724							}
3725						}
3726						else if (value.tag)
3727						{
3728							var
3729								className = value.className,
3730								tag = value.tag.toUpperCase();
3731
3732							// Inline
3733							if (tag == 'SPAN')
3734							{
3735								//command, value, tagName, arStyle, cssClass, params)
3736								_this.actions.formatInline.exec(action, value, tag, false, className);
3737							}
3738							else //if (tag == 'P')
3739							{
3740								_this.actions.formatBlock.exec('formatBlock', tag, className, null, {nestedBlocks: false});
3741							}
3742						}
3743
3744						if (!_this.editor.util.FirstLetterSupported())
3745						{
3746							_this.editor.parser.FirstLetterCheckNodes('', '', true);
3747						}
3748					}
3749				},
3750
3751				state: function(action, value)
3752				{
3753					var
3754						result = false,
3755						selectedNode = _this.editor.selection.GetSelectedNode();
3756
3757					if (selectedNode)
3758					{
3759						if (isNodeSuitable(selectedNode))
3760						{
3761							result = selectedNode;
3762						}
3763						else
3764						{
3765							result = BX.findParent(selectedNode, isNodeSuitable, _this.document.body);
3766						}
3767					}
3768					else
3769					{
3770						var
3771							range = _this.editor.selection.GetRange(),
3772							commonAncestor = _this.editor.selection.GetCommonAncestorForRange(range);
3773
3774						if (isNodeSuitable(commonAncestor))
3775						{
3776							result = commonAncestor;
3777						}
3778					}
3779
3780					return result;
3781				},
3782				value: BX.DoNothing
3783			};
3784		},
3785
3786		GetChangeTemplate: function()
3787		{
3788			var _this = this;
3789			return {
3790				exec: function(action, value)
3791				{
3792					_this.editor.ApplyTemplate(value);
3793				},
3794
3795				state: function(action, value)
3796				{
3797					return _this.editor.GetTemplateId();
3798				},
3799
3800				value: BX.DoNothing
3801			};
3802		},
3803
3804		GetSelectNode: function()
3805		{
3806			var _this = this;
3807			return {
3808				exec: function(action, node)
3809				{
3810					if (!_this.editor.iframeView.IsFocused())
3811					{
3812						_this.editor.iframeView.Focus();
3813					}
3814
3815					if (node === false || (node && node.nodeName == 'BODY')) // Select all
3816					{
3817						if (_this.IsSupportedByBrowser('SelectAll'))
3818						{
3819							_this.document.execCommand('SelectAll');
3820						}
3821						else
3822						{
3823							_this.editor.selection.SelectNode(node);
3824						}
3825					}
3826					else
3827					{
3828						_this.editor.selection.SelectNode(node);
3829
3830					}
3831				},
3832
3833				state: BX.DoNothing,
3834				value: BX.DoNothing
3835			};
3836		},
3837
3838		GetUndoRedo: function(bUndo)
3839		{
3840			var _this = this;
3841			return {
3842				exec: function(action)
3843				{
3844					if (action == 'doUndo')
3845					{
3846						_this.editor.undoManager.Undo();
3847					}
3848					else if(action == 'doRedo')
3849					{
3850						_this.editor.undoManager.Redo();
3851					}
3852				},
3853				state: BX.DoNothing,
3854				value: BX.DoNothing
3855			};
3856		},
3857
3858		GetUniversalFormatStyle: function()
3859		{
3860			var
3861				TEMP_CLASS = 'bx-tmp-ufs-class',
3862				STATUS_ATTR = 'data-bx-tmp-status',
3863				_this = this;
3864
3865			function getAncestorNodes(nodes)
3866			{
3867				var result = [];
3868				if (nodes && nodes.length > 0)
3869				{
3870					var
3871						i, len, sorted = [], node, status;
3872					for (i = 0, len = nodes.length; i < len; i++)
3873					{
3874						if (!_this.editor.util.CheckSurrogateNode(nodes[i]) && nodes[i].nodeName !== 'BR')
3875						{
3876							nodes[i].setAttribute(STATUS_ATTR, 'Y');
3877							sorted.push({node: nodes[i], nesting: _this.editor.util.GetNodeDomOffset(nodes[i])});
3878						}
3879					}
3880					sorted = sorted.sort(function(a, b){return a.nesting - b.nesting});
3881					for (i = 0, len = sorted.length; i < len; i++)
3882					{
3883						node = sorted[i].node;
3884						status = node.getAttribute(STATUS_ATTR);
3885						if (status == 'Y' && !findUnIncludedNodes(node))
3886						{
3887							// Prevent including all childs of this node to result
3888							BX.findChild(node, function(n)
3889							{
3890								if (n.nodeType == 1 && n.nodeName !== 'BR' && n.setAttribute)
3891								{
3892									n.setAttribute(STATUS_ATTR, n.className == TEMP_CLASS ? 'GET_RID_OF' : 'SKIP');
3893								}
3894								return false;
3895							}, true, true);
3896
3897							result.push(node);
3898						}
3899					}
3900				}
3901				return result;
3902			}
3903
3904			function findUnIncludedNodes(node)
3905			{
3906				var res = BX.findChild(node, function(n)
3907				{
3908					return n.nodeType == 1 && n.nodeName !== 'BR' && n.getAttribute && n.getAttribute(STATUS_ATTR) !== 'Y';
3909				}, true, false);
3910				return !!res;
3911			}
3912
3913			function adjustNodeStyle(node, className, style)
3914			{
3915				try
3916				{
3917					if (className !== false)
3918					{
3919						if (className == '')
3920						{
3921							node.removeAttribute('class');
3922						}
3923						else
3924						{
3925							node.className = className;
3926						}
3927					}
3928					if (style !== false)
3929					{
3930						if (style == '')
3931						{
3932							node.removeAttribute('style');
3933						}
3934						else
3935						{
3936							node.style.cssText = style;
3937						}
3938					}
3939				}
3940				catch(e)
3941				{}
3942			}
3943
3944			return {
3945				exec: function(action, value)
3946				{
3947					if (value.nodes && value.nodes.length > 0)
3948					{
3949						for (i = 0; i < value.nodes.length; i++)
3950						{
3951							adjustNodeStyle(value.nodes[i], value.className, value.style);
3952						}
3953					}
3954					else
3955					{
3956						_this.actions.formatInline.exec(action, value, "span", false, TEMP_CLASS);
3957
3958						if (document.querySelectorAll)
3959						{
3960							var tmpSpanNodes = _this.editor.GetIframeDoc().querySelectorAll('.' + TEMP_CLASS);
3961							if (tmpSpanNodes)
3962							{
3963								for (i = 0; i < tmpSpanNodes.length; i++)
3964								{
3965									node = tmpSpanNodes[i];
3966									if (BX.util.trim(node.innerHTML) == '')
3967									{
3968										_this.editor.util.ReplaceWithOwnChildren(node);
3969									}
3970								}
3971							}
3972						}
3973
3974						var
3975							i, node,
3976							nodes = _this.actions.universalFormatStyle.state(action),
3977							existNodes = getAncestorNodes(nodes);
3978
3979						for (i = 0; i < existNodes.length; i++)
3980						{
3981							adjustNodeStyle(existNodes[i], value.className, value.style);
3982						}
3983
3984						if (document.querySelectorAll)
3985						{
3986							tmpSpanNodes = _this.editor.GetIframeDoc().querySelectorAll('.' + TEMP_CLASS);
3987							if (tmpSpanNodes)
3988							{
3989								for (i = 0; i < tmpSpanNodes.length; i++)
3990								{
3991									node = tmpSpanNodes[i];
3992									if (node.getAttribute(STATUS_ATTR) == 'GET_RID_OF')
3993									{
3994										_this.editor.util.ReplaceWithOwnChildren(tmpSpanNodes[i]);
3995									}
3996									else
3997									{
3998										adjustNodeStyle(tmpSpanNodes[i], value.className, value.style);
3999									}
4000								}
4001							}
4002						}
4003					}
4004				},
4005				state: function(action)
4006				{
4007					var range = _this.editor.selection.GetRange();
4008
4009					if (range)
4010					{
4011						var
4012							textNodes, textNode, node,
4013							nodes = range.getNodes([1]);
4014
4015						// Range is collapsed or text node is selected
4016						if (nodes.length == 0)
4017						{
4018							textNodes = range.getNodes([3]);
4019							if (textNodes && textNodes.length == 1)
4020							{
4021								textNode = textNodes[0];
4022							}
4023
4024							if (!textNode && range.startContainer == range.endContainer)
4025							{
4026								if (range.startContainer.nodeType == 3)
4027								{
4028									textNode = range.startContainer;
4029								}
4030								else
4031								{
4032									_this.editor.selection.SelectNode(range.startContainer);
4033									nodes = [range.startContainer];
4034								}
4035							}
4036
4037							if (textNode && nodes.length == 0)
4038							{
4039								node = textNode.parentNode;
4040								if (node)
4041								{
4042									nodes = [node];
4043								}
4044							}
4045						}
4046						return nodes;
4047					}
4048				},
4049				value: BX.DoNothing
4050			};
4051		},
4052
4053		GetSubSup: function(type)
4054		{
4055			var _this = this;
4056
4057			type = type == 'sup' ? 'sup' : 'sub';
4058
4059			return {
4060				exec: function(action, value)
4061				{
4062					return _this.actions.formatInline.exec(action, value, type);
4063				},
4064				state: function(action, value)
4065				{
4066					return _this.actions.formatInline.state(action, value, type);
4067				},
4068				value: BX.DoNothing
4069			};
4070		},
4071
4072		GetQuote: function()
4073		{
4074			var
4075				range,
4076				externalSelection,
4077				_this = this;
4078
4079			function checkNode(n)
4080			{
4081				return n && n.className == 'bxhtmled-quote' && n.nodeName == 'BLOCKQUOTE';
4082			}
4083
4084			function setExternalSelection(text)
4085			{
4086				externalSelection = text;
4087			}
4088			function getExternalSelection()
4089			{
4090				return externalSelection;
4091			}
4092			function setRange(rng)
4093			{
4094				return range = rng;
4095			}
4096			function setExternalSelectionFromRange(range)
4097			{
4098				range = range || _this.editor.selection.GetRange(_this.editor.selection.GetSelection(document));
4099
4100				if (range)
4101				{
4102					const nodeTypeText = 3;
4103					const isTextSelected = range.startContainer.nodeType === nodeTypeText && range.startContainer === range.endContainer;
4104					const isFileNameSelected = isTextSelected && range.startContainer.parentNode.hasAttribute('bx-attach-file-id');
4105					if (isFileNameSelected)
4106					{
4107						// select node next to text to put the <a> tag to a quote
4108						const startContainer = range.startContainer;
4109						range.setStartBefore(startContainer.parentElement);
4110						range.setEndAfter(startContainer);
4111					}
4112					var tmpDiv;
4113					// mantis:64329
4114					if (range.startContainer == range.endContainer && range.startOffset == 0 && range.endOffset == range.endContainer.length && range.startContainer.parentNode && range.startContainer.parentNode.nodeName == 'A' && range.startContainer.parentNode.href)
4115					{
4116						tmpDiv = BX.create('DIV', {html: range.startContainer.parentNode.href}, _this.editor.GetIframeDoc());
4117					}
4118					else
4119					{
4120						var html = range.toHtml();
4121						html = html.replace(/<br.*?>/ig, "#BX_BR#");
4122						tmpDiv = BX.create('DIV', {html: BX.util.htmlspecialchars(html)}, _this.editor.GetIframeDoc());
4123					}
4124
4125					var extSel = _this.editor.util.GetTextContentEx(tmpDiv);
4126					extSel = extSel.replace(/#BX_BR#/ig, "<br>");
4127					setExternalSelection(extSel);
4128					BX.remove(tmpDiv);
4129				}
4130				else
4131				{
4132					setExternalSelection('');
4133				}
4134			}
4135
4136			function checkSpaceAfterQuotes(target)
4137			{
4138				var
4139					i, quote,
4140					quotes = target.querySelectorAll("blockquote.bxhtmled-quote");
4141
4142				for (i = 0; i < quotes.length; i++)
4143				{
4144					quote = quotes[i];
4145					if (quote.nextSibling === null)
4146					{
4147						_this.editor.util.InsertAfter(_this.editor.util.GetInvisibleTextNode(), quote);
4148					}
4149				}
4150			}
4151
4152			return {
4153				exec: function(action)
4154				{
4155					var
4156						res = false,
4157						sel = getExternalSelection();
4158
4159					if (_this.editor.bbCode && _this.editor.synchro.IsFocusedOnTextarea())
4160					{
4161						_this.editor.textareaView.Focus();
4162						if(sel)
4163						{
4164							res = _this.editor.textareaView.WrapWith(false, false, "[QUOTE]" + sel + "[/QUOTE]");
4165						}
4166						else
4167						{
4168							res = _this.actions.formatBbCode.exec(action, {tag: 'QUOTE'});
4169						}
4170					}
4171					else
4172					{
4173						range = _this.editor.iframeView.GetSelection().getRangeAt(0);
4174						_this.editor.iframeView.Focus();
4175						if (range)
4176						{
4177							_this.editor.selection.SetSelection(range);
4178						}
4179
4180						if(sel)
4181						{
4182							var quoteId = 'bxq_' + Math.round(Math.random() * 1000000);
4183
4184							_this.editor.InsertHtml('<blockquote id="' + quoteId + '" class="bxhtmled-quote">' + sel + '</blockquote>' + _this.editor.INVISIBLE_SPACE, range);
4185
4186							setTimeout(function()
4187							{
4188								var quote = _this.editor.GetIframeElement(quoteId);
4189								if (quote)
4190								{
4191									var prev = quote.previousSibling;
4192									if (prev && prev.nodeType == 3 && _this.editor.util.IsEmptyNode(prev) &&
4193										prev.previousSibling && prev.previousSibling.nodeName == 'BR')
4194									{
4195										BX.remove(prev);
4196									}
4197									quote.removeAttribute('id');
4198								}
4199							}, 0);
4200						}
4201						else
4202						{
4203							if (!range && _this.editor.selection.lastRange)
4204								range = _this.editor.selection.lastRange;
4205
4206							res = _this.actions.formatBlock.exec('formatBlock', 'blockquote', 'bxhtmled-quote', false, {range: range});
4207						}
4208
4209						if(!sel)
4210							_this.editor.selection.ScrollIntoView();
4211					}
4212
4213					range = null;
4214					return res;
4215				},
4216				state: function()
4217				{
4218					return _this.actions.formatBlock.state('formatBlock', 'blockquote', 'bxhtmled-quote');
4219				},
4220				value: BX.DoNothing,
4221				setExternalSelectionFromRange : setExternalSelectionFromRange,
4222				setExternalSelection : setExternalSelection,
4223				getExternalSelection : getExternalSelection,
4224				checkSpaceAfterQuotes: checkSpaceAfterQuotes,
4225				setRange : setRange,
4226				checkNode: checkNode
4227			};
4228		},
4229
4230		GetCode: function()
4231		{
4232			var _this = this;
4233			return {
4234				exec: function(action)
4235				{
4236					// Iframe
4237					if (!_this.editor.bbCode || !_this.editor.synchro.IsFocusedOnTextarea())
4238					{
4239						var codeElement = _this.actions.code.state();
4240						if (codeElement)
4241						{
4242							var innerHtml = BX.util.trim(codeElement.innerHTML);
4243							if (innerHtml == '<br>' || innerHtml === '')
4244							{
4245								_this.editor.selection.SetAfter(codeElement);
4246								BX.remove(codeElement);
4247							}
4248							else
4249							{
4250								_this.editor.selection.ExecuteAndRestoreSimple(function()
4251								{
4252									codeElement.className = '';
4253									codeElement = _this.editor.util.RenameNode(codeElement, 'P');
4254								});
4255							}
4256						}
4257						else
4258						{
4259							_this.actions.formatBlock.exec('formatBlock', 'pre', 'bxhtmled-code');
4260						}
4261					}
4262					else // bbcode + textarea
4263					{
4264						return _this.actions.formatBbCode.exec(action, {tag: 'CODE'});
4265					}
4266				},
4267				state: function()
4268				{
4269					return _this.actions.formatBlock.state('formatBlock', 'pre', 'bxhtmled-code');
4270				},
4271				value: BX.DoNothing
4272			};
4273		},
4274
4275		GetInsertSmile: function()
4276		{
4277			var _this = this;
4278			return {
4279				exec: function(action, value)
4280				{
4281					var smile = _this.editor.smilesIndex[value];
4282					if (_this.editor.bbCode && _this.editor.synchro.IsFocusedOnTextarea())
4283					{
4284						_this.editor.textareaView.Focus();
4285						_this.editor.textareaView.WrapWith(false, false, " " + smile.code + " ");
4286					}
4287					else
4288					{
4289						_this.editor.iframeView.Focus();
4290						if (smile)
4291						{
4292							var smileImg = BX.create("IMG", {props:
4293							{
4294								src: smile.path,
4295								title: smile.name || smile.code
4296							}}, _this.editor.iframeView.document);
4297							_this.editor.SetBxTag(smileImg, {tag: "smile", params: smile});
4298							if (smile.width)
4299								smileImg.style.width = parseInt(smile.width) + 'px';
4300							if (smile.height)
4301								smileImg.style.height = parseInt(smile.height) + 'px';
4302							_this.editor.selection.InsertNode(smileImg);
4303
4304							var textBefore = _this.editor.iframeView.document.createTextNode(' ');
4305							smileImg.parentNode.insertBefore(textBefore, smileImg);
4306							var textAfer = BX.create("SPAN", {html: '&nbsp;'}, _this.editor.iframeView.document);
4307							_this.editor.util.InsertAfter(textAfer, smileImg);
4308							_this.editor.selection.SetAfter(textAfer);
4309							setTimeout(function(){_this.editor.selection.SetAfter(textAfer);}, 10);
4310						}
4311					}
4312				},
4313				state: BX.DoNothing,
4314				value: BX.DoNothing
4315			};
4316		},
4317
4318		GetTableOperation: function()
4319		{
4320			var
4321				_this = this,
4322				newCellHtml = '&nbsp;';
4323
4324			function createTableMatrix(oTable)
4325			{
4326				var aRows = oTable.rows;
4327				// Row and Column counters.
4328				var
4329					arMatrix = [],
4330					i, // index
4331					c, j, oCell, rs, cs,
4332					iColSpan, iRowSpan,
4333					r = -1; // row
4334
4335				for (i = 0; i < aRows.length; i++)
4336				{
4337					r++;
4338					if (!arMatrix[r])
4339					{
4340						arMatrix[r] = [];
4341					}
4342
4343					c = -1;
4344
4345					for (j = 0; j < aRows[i].cells.length; j++)
4346					{
4347						oCell = aRows[i].cells[j];
4348
4349						c++;
4350						while (arMatrix[r][c])
4351						{
4352							c++;
4353						}
4354
4355						iColSpan = isNaN(oCell.colSpan) ? 1 : oCell.colSpan;
4356						iRowSpan = isNaN(oCell.rowSpan) ? 1 : oCell.rowSpan;
4357
4358						for(rs = 0; rs < iRowSpan; rs++)
4359						{
4360							if (!arMatrix[r + rs])
4361							{
4362								arMatrix[r + rs] = [];
4363							}
4364
4365							for (cs = 0;
4365 cs < iColSpan; cs++)
4366							{
4367								arMatrix[r + rs][c + cs] = aRows[i].cells[j];
4368							}
4369						}
4370
4371						c += iColSpan - 1;
4372					}
4373				}
4374				return arMatrix;
4375			}
4376
4377			function getIndexes(oCell, arMatrix)
4378			{
4379				var
4380					i, j,
4381					arIndexes = [];
4382
4383				for (i = 0; i < arMatrix.length; i++)
4384				{
4385					for (j = 0, l = arMatrix[i].length; j < l; j++)
4386					{
4387						if (arMatrix[i][j] == oCell)
4388						{
4389							arIndexes.push({r : i, c : j});
4390						}
4391					}
4392				}
4393				return arIndexes;
4394			}
4395
4396			function getCellIndexInfo(ind)
4397			{
4398				var
4399					rows = [], cols = [],
4400					indInfo = {
4401						cells: 0
4402					},
4403					ii;
4404
4405				for(ii = 0; ii < ind.length; ii++)
4406				{
4407					indInfo.cells++;
4408					indInfo.maxRow = ii === 0 ? ind[ii].r : Math.max(ind[ii].r, indInfo.maxRow);
4409					indInfo.minRow = ii === 0 ? ind[ii].r : Math.min(ind[ii].r, indInfo.minRow);
4410					indInfo.maxCol = ii === 0 ? ind[ii].c : Math.max(ind[ii].c, indInfo.maxCol);
4411					indInfo.minCol = ii === 0 ? ind[ii].c : Math.min(ind[ii].c, indInfo.minCol);
4412
4413					if (!BX.util.in_array(ind[ii].r, rows))
4414						rows.push(ind[ii].r);
4415
4416					if (!BX.util.in_array(ind[ii].c, cols))
4417						cols.push(ind[ii].c);
4418				}
4419
4420				indInfo.rows = rows.length;
4421				indInfo.cols = cols.length;
4422
4423				return indInfo;
4424			}
4425
4426			function findAndPushAndUniqueCell(cells, node, table)
4427			{
4428				if (node)
4429				{
4430					node = getParentCell(node, table);
4431
4432					if (node && !BX.util.in_array(node, cells))
4433						cells.push(node);
4434				}
4435
4436				return cells;
4437			}
4438
4439			function getSelectedCells(range, table)
4440			{
4441				var cells = [], cell;
4442
4443				if (BX.browser.IsFirefox())
4444				{
4445					var
4446						i, rng, start, end,
4447						sel = rangy.getNativeSelection(_this.editor.sandbox.GetWindow());
4448
4449					for (i = 0; i < sel.rangeCount; i++)
4450					{
4451						rng = sel.getRangeAt(i);
4452
4453						start = rng.startContainer.nodeType === 1 ? rng.startContainer.childNodes[rng.startOffset] : rng.startContainer;
4454						end = rng.endContainer.nodeType === 1 ? rng.endContainer.childNodes[rng.endOffset] : rng.endContainer;
4455
4456						cells = findAndPushAndUniqueCell(cells, start, table);
4457						cells = findAndPushAndUniqueCell(cells, end, table);
4458					}
4459				}
4460				else
4461				{
4462					if (range.collapsed)
4463					{
4464						cell = getParentCell(range.startContainer);
4465						cells = findAndPushAndUniqueCell(cells, cell, table);
4466					}
4467					else
4468					{
4469						var nodes = range.getNodes([1]);
4470						for (i = 0; i < nodes.length; i++)
4471						{
4472							if (nodes[i].nodeName == 'TD' || nodes[i].nodeName == 'TH')
4473							{
4474								cells = findAndPushAndUniqueCell(cells, nodes[i], table);
4475							}
4476						}
4477					}
4478				}
4479
4480				return cells;
4481			}
4482
4483			function insertColumn(element, table, actionType)
4484			{
4485				var td = BX.findParent(element, {tag: 'TD'});
4486
4487				if (!td)
4488					return;
4489
4490				var
4491					tr = td.parentNode,
4492					cellInd = actionType == 'insertColumnLeft' ? td.cellIndex : td.cellIndex + 1,
4493					rowInd = tr.rowIndex,
4494					mtx = createTableMatrix(table),
4495					arInd = getIndexes(td, mtx);
4496
4497				tr.insertCell(cellInd).innerHTML = newCellHtml;
4498
4499				var
4500					r, ind, i, c, j,
4501					curFullCellInd = actionType == 'insertColumnLeft' ? arInd[0].c : arInd[0].c + 1;
4502
4503				for (j = 0; j < table.rows.length; j++)
4504				{
4505					r = table.rows[j];
4506					if (r.rowIndex == rowInd)
4507					{
4508						continue;
4509					}
4510
4511					ind = 0;
4512					i = 0;
4513					for(i = 0; i < r.cells.length; i++)
4514					{
4515						c = r.cells[i];
4516						arInd = getIndexes(c, mtx);
4517						if (arInd[0].c >= curFullCellInd)
4518						{
4519							ind = c.cellIndex;
4520							break;
4521						}
4522						ind = i + 1;
4523					}
4524
4525					r.insertCell(ind).innerHTML = '&nbsp;';
4526				}
4527			}
4528
4529			function insertRow(element, table, actionType)
4530			{
4531				var tr = BX.findParent(element, {tag: 'TR'});
4532				if (!tr || !table)
4533					return;
4534
4535				var
4536					i, newCell,
4537					rowInd = actionType == 'insertRowUpper' ? tr.rowIndex : tr.rowIndex + 1,
4538					newRow = table.insertRow(rowInd);
4539
4540				for(i = 0; i < tr.cells.length; i++)
4541				{
4542					newCell = newRow.insertCell(i);
4543					newCell.innerHTML = newCellHtml;
4544					newCell.colSpan = tr.cells[i].colSpan;
4545				}
4546			}
4547
4548			function insertCell(element, table, actionType)
4549			{
4550				var td = getParentCell(element, table);
4551				if (!td || !table)
4552					return;
4553
4554				var
4555					tr = td.parentNode,
4556					cellInd = actionType == 'insertCellLeft' ? td.cellIndex : td.cellIndex + 1;
4557
4558				tr.insertCell(cellInd).innerHTML = newCellHtml;
4559			}
4560
4561			function getParentCell(node, table)
4562			{
4563				if (node.nodeName == 'TD' || node.nodeName == 'TH')
4564					return node;
4565
4566				return BX.findParent(node, function(n)
4567				{
4568					return n.nodeName == 'TD' || n.nodeName == 'TH'
4569				}, table);
4570			}
4571
4572
4573			function getMergeState(cells, table)
4574			{
4575				var
4576					indInfo, i,
4577					mtx = createTableMatrix(table),
4578					firstIndInfo = getCellIndexInfo(getIndexes(cells[0], mtx)),
4579					lastIndInfo = firstIndInfo,
4580					gaps = false,
4581					sameRow = true,
4582					sameCol = true;
4583
4584				for(i = 1; i < cells.length; i++)
4585				{
4586					indInfo = getCellIndexInfo(getIndexes(cells[i], mtx));
4587					sameRow = sameRow && indInfo.rows == firstIndInfo.rows && indInfo.maxRow == firstIndInfo.maxRow  && indInfo.minRow == firstIndInfo.minRow;
4588					sameCol = sameCol && indInfo.cols == firstIndInfo.cols && indInfo.maxCol == firstIndInfo.maxCol  && indInfo.minCol == firstIndInfo.minCol;
4589
4590					gaps = gaps ||
4591						(sameRow && Math.abs(indInfo.minCol - lastIndInfo.maxCol) > 1)
4592						||
4593						(sameCol && Math.abs(indInfo.minRow - lastIndInfo.maxRow) > 1)
4594						||
4595						!sameRow && !sameCol;
4596
4597					lastIndInfo = indInfo;
4598				}
4599
4600				return {
4601					sameCol : sameCol,
4602					sameRow: sameRow,
4603					gaps: gaps
4604				};
4605			}
4606
4607			function canBeMerged(cells, range, table)
4608			{
4609				if (!cells)
4610					cells = getSelectedCells(range, table);
4611
4612				if (!cells || cells.length < 2)
4613					return false;
4614
4615				var mergeState = getMergeState(cells, table);
4616				return !mergeState.gaps && (!mergeState.sameRow && mergeState.sameCol || mergeState.sameRow && !mergeState.sameCol);
4617			}
4618
4619			function canBeMergedWithRight(range, table)
4620			{
4621				var cells = getSelectedCells(range, table);
4622				if (!cells || cells.length !== 1)
4623					return false;
4624
4625				var
4626					mtx = createTableMatrix(table),
4627					ind = getIndexes(cells[0], mtx);
4628
4629				if (ind.length < 1)
4630					return false;
4631
4632				var
4633					i, rightTd,
4634					maxCol = ind[ind.length - 1].c, // Max col
4635					res = true, c;
4636
4637				for (i = 0; i < ind.length; i++)
4638				{
4639					if (ind[i].c == maxCol)
4640					{
4641						if (mtx[ind[i].r] && mtx[ind[i].r][ind[i].c + 1])
4642						{
4643							c = mtx[ind[i].r][ind[i].c + 1];
4644
4645							if (rightTd === undefined)
4646								rightTd = c;
4647							else if (rightTd !== c)
4648								res = false;
4649						}
4650						else
4651						{
4652							res = false;
4653						}
4654					}
4655				}
4656
4657				res = res && rightTd && canBeMerged([cells[0], rightTd], range, table);
4658
4659				return res;
4660			}
4661
4662			function canBeMergedWithBottom(range, table)
4663			{
4664				var cells = getSelectedCells(range, table);
4665				if (!cells || cells.length !== 1)
4666					return false;
4667
4668				var
4669					mtx = createTableMatrix(table),
4670					ind = getIndexes(cells[0], mtx);
4671
4672				if (ind.length < 1)
4673					return false;
4674
4675				var
4676					i, bottomTd,
4677					maxRow = ind[ind.length - 1].r, // Max row
4678					res = true, c;
4679
4680				for (i = 0; i < ind.length; i++)
4681				{
4682					if (ind[i].r == maxRow)
4683					{
4684						if (mtx[maxRow + 1] && mtx[maxRow + 1][ind[i].c])
4685						{
4686							c = mtx[maxRow + 1][ind[i].c];
4687							if (bottomTd === undefined)
4688								bottomTd = c;
4689							else if (bottomTd !== c)
4690								res = false;
4691						}
4692						else
4693						{
4694							res = false;
4695						}
4696					}
4697				}
4698
4699				res = res && bottomTd && canBeMerged([cells[0], bottomTd], range, table);
4700
4701				return res;
4702			}
4703
4704			function mergeCells(range, table, cells)
4705			{
4706				if (!cells)
4707					cells = getSelectedCells(range, table);
4708
4709				if (cells.length < 2)
4710					return;
4711
4712				var
4713					mergeState = getMergeState(cells, table),
4714					i, tr,
4715					newCellColSpan = 0,
4716					newCellRowSpan = 0,
4717					newCellContent = '';
4718
4719				// Horizontal cells
4720				if (mergeState.sameRow && !mergeState.sameCol && !mergeState.gaps)
4721				{
4722					for(i = 0; i < cells.length; i++)
4723					{
4724						newCellContent += ' ' + BX.util.trim(cells[i].innerHTML);
4725						tr = cells[i].parentNode;
4726						newCellColSpan += cells[i].colSpan;
4727
4728						if (i > 0)
4729							tr.removeChild(cells[i]);
4730					}
4731
4732					cells[0].colSpan = newCellColSpan;
4733					cells[0].innerHTML = BX.util.trim(newCellContent);
4734				}
4735				// vertical cells
4736				else if (!mergeState.sameRow && mergeState.sameCol && !mergeState.gaps)
4737				{
4738					for(i = 0; i < cells.length; i++)
4739					{
4740						newCellContent += ' ' + BX.util.trim(cells[i].innerHTML);
4741						tr = cells[i].parentNode;
4742						newCellRowSpan += cells[i].rowSpan;
4743
4744						if (i > 0)
4745							tr.removeChild(cells[i]);
4746					}
4747
4748					cells[0].rowSpan = newCellRowSpan;
4749					cells[0].innerHTML = BX.util.trim(newCellContent);
4750				}
4751				else
4752				{
4753					alert(BX.message('BXEdTableMergeError'));
4754				}
4755			}
4756
4757			function mergeRightCell(range, table)
4758			{
4759				var cells = getSelectedCells(range, table);
4760
4761				if (!cells || cells.length !== 1)
4762					return false;
4763
4764				var tr = BX.findParent(cells[0], {tag: 'TR'}, table);
4765
4766				if (cells[0].cellIndex < tr.cells.length - 1)
4767				{
4768					cells.push(tr.cells[cells[0].cellIndex + 1]);
4769				}
4770
4771				return mergeCells(range, table, cells);
4772			}
4773
4774			function mergeBottomCell(range, table)
4775			{
4776				var cells = getSelectedCells(range, table);
4777				if (!cells || cells.length !== 1)
4778					return false;
4779
4780				var
4781					mtx = createTableMatrix(table),
4782					ind = getIndexes(cells[0], mtx),
4783					i, bottomTd,
4784					maxRow = ind[ind.length - 1].r, // Max row
4785					res = true, c;
4786
4787				for (i = 0; i < ind.length; i++)
4788				{
4789					if (ind[i].r == maxRow)
4790					{
4791						if (mtx[maxRow + 1] && mtx[maxRow + 1][ind[i].c])
4792						{
4793							c = mtx[maxRow + 1][ind[i].c];
4794							if (bottomTd === undefined)
4795								bottomTd = c;
4796							else if (bottomTd !== c)
4797								res = false;
4798						}
4799						else
4800						{
4801							res = false;
4802						}
4803					}
4804				}
4805
4806				if (res)
4807				{
4808					cells.push(bottomTd);
4809					return mergeCells(range, table, cells);
4810				}
4811			}
4812
4813			function mergeRow(range, table)
4814			{
4815				var cells = getSelectedCells(range, table);
4816				if (!cells || cells.length !== 1)
4817					return false;
4818
4819				var
4820					i, newCells = [],
4821					tr = cells[0].parentNode;
4822
4823				for(i = 0; i < tr.cells.length; i++)
4824				{
4825					newCells.push(tr.cells[i]);
4826				}
4827
4828				return mergeCells(range, table, newCells);
4829			}
4830
4831			function mergeColumn(range, table)
4832			{
4833				var cells = getSelectedCells(range, table);
4834				if (!cells || cells.length !== 1)
4835					return false;
4836
4837				var
4838					i, j, newCells = [],
4839					mtx = createTableMatrix(table),
4840					indInfo = getCellIndexInfo(getIndexes(cells[0], mtx));
4841
4842				for (i = 0; i < mtx.length; i++)
4843				{
4844					for (j = indInfo.minCol; j <= indInfo.minCol; j++)
4845					{
4846						newCells = findAndPushAndUniqueCell(newCells, mtx[i][j], table);
4847					}
4848				}
4849
4850				return mergeCells(range, table, newCells);
4851			}
4852
4853			function splitHorizontally(range, table)
4854			{
4855				var cells = getSelectedCells(range, table);
4856
4857				if (!cells || cells.length != 1)
4858					return false;
4859
4860				var
4861					i, j,
4862					realInd = 0,
4863					realIndI,
4864					trI,
4865					newCell,
4866					colSpan = cells[0].colSpan,
4867					rowSpan = cells[0].rowSpan,
4868					tr = cells[0].parentNode;
4869
4870				for(i = 0; i <= cells[0].cellIndex; i++)
4871					realInd += tr.cells[i].colSpan;
4872
4873				if (colSpan > 1)
4874				{
4875					cells[0].colSpan--;
4876				}
4877				else
4878				{
4879					for(j = 0; j < table.rows.length; j++)
4880					{
4881						if (j == tr.rowIndex || j >= tr.rowIndex && j < tr.rowIndex + rowSpan)
4882							continue;
4883
4884						realIndI = 0;
4885						trI = table.rows[j];
4886
4887						i = 0;
4888						while (realIndI < realInd && i < trI.cells.length)
4889							realIndI += trI.cells[i++].colSpan;
4890
4891						i--;
4892						trI.cells[i].colSpan += 1;
4893
4894						// mantis: 71909
4895						if (trI.cells[i].rowSpan > 1)
4896							j = j + trI.cells[i].rowSpan - 1;
4897					}
4898				}
4899				newCell = tr.insertCell(cells[0].cellIndex + 1);
4900				newCell.rowSpan = cells[0].rowSpan;
4901				newCell.innerHTML = newCellHtml;
4902			}
4903
4904			function splitVertically(range, table)
4905			{
4906				var cells = getSelectedCells(range, table);
4907
4908				if (!cells || cells.length != 1)
4909					return false;
4910
4911				var
4912					i, r, c, row, cell,
4913					indI,
4914					fullRowInd, realCellInd,
4915					mtx = createTableMatrix(table),
4916					ind = getIndexes(cells[0], mtx),
4917					tr = cells[0].parentNode,
4918					//maxCellCount = arTMX[0].length; //max count of cell in table
4919					curRowIndex = tr.rowIndex,
4920					curCellIndex = cells[0].cellIndex,
4921					curFullRowInd = ind[0].r,
4922					curFullCellInd = ind[0].c,
4923					bOneW = true,
4924					bOneH = true;
4925
4926				for(i = 1; i < ind.length; i++)
4927				{
4928					if (ind[i].r != curFullRowInd)
4929						bOneH = false;
4930					if (ind[i].c != curFullCellInd)
4931						bOneW = false;
4932				}
4933
4934				if (bOneH) // if rowSpan == 1 and we have to split this cell
4935				{
4936					var
4937						newRow = table.insertRow(tr.rowIndex + 1),
4938						newCell = newRow.insertCell(-1);
4939
4940					newCell.innerHTML = newCellHtml;
4941					if (!bOneW)
4942						newCell.colSpan = cells[0].colSpan;
4943
4944
4945					for(r = 0; r <= curFullRowInd; r++)
4946					{
4947						row = table.rows[r];
4948						for(c = 0; c < row.cells.length; c++)
4949						{
4950							cell = row.cells[c];
4951							if (r == curRowIndex && c == curCellIndex)
4952								continue;
4953
4954							fullRowInd = r; // oRow.rowIndex
4955							if (cell.rowSpan > 1)
4956								fullRowInd += cell.rowSpan - 1;
4957
4958							if (fullRowInd >= curFullRowInd)
4959								cell.rowSpan++;
4960						}
4961					}
4962				}
4963				else // If cell has rowspan > 1
4964				{
4965					row = table.rows[curRowIndex + --cells[0].rowSpan];
4966					realCellInd = false;
4967					for(c = 0; c < row.cells.length; c++)
4968					{
4969						indI = getIndexes(row.cells[c], mtx);
4970						for(i = 0; i < indI.length; i++)
4971						{
4972							if (indI[i].c > curCellIndex)
4973								realCellInd = 0;
4974							else if (indI[i].c + 1 == curCellIndex)
4975								realCellInd = row.cells[c].cellIndex + 1;
4976
4977							if (realCellInd !== false)
4978								break;
4979						}
4980					}
4981
4982					newCell = row.insertCell(realCellInd);
4983					newCell.innerHTML = newCellHtml;
4984					if (!bOneW)
4985						newCell.colSpan = cells[0].colSpan;
4986				}
4987			}
4988
4989			// Remove
4990			function removeColumn(range, table)
4991			{
4992				var cells = getSelectedCells(range, table);
4993				if (!cells || cells.length != 1)
4994					return false;
4995				var cell = cells[0];
4996
4997				if (!cell)
4998					return false;
4999
5000				var
5001					tr, td,
5002					mtx = createTableMatrix(table),
5003					ind = getIndexes(cell, mtx),
5004					j;
5005
5006				for (j = 0; j < mtx.length; j++)
5007				{
5008					td = mtx[j][ind[0].c];
5009					if (td && td.parentNode)
5010					{
5011						tr = td.parentNode;
5012						BX.remove(td);
5013						if (tr.cells.length == 0)
5014							BX.remove(tr);
5015					}
5016				}
5017
5018				if (table.rows.length == 0)
5019					BX.remove(table);
5020			}
5021
5022			function removeRow(range, table)
5023			{
5024				var cells = getSelectedCells(range, table);
5025				if (!cells || cells.length != 1)
5026					return false;
5027				var cell = cells[0];
5028
5029
5030				if (!cell)
5031					return false
5032
5033				BX.remove(cell.parentNode);
5034
5035				if (table.rows.length == 0)
5036					BX.remove(table);
5037			}
5038
5039			function removeCell(range, table, cell)
5040			{
5041				if (!cell)
5042				{
5043					var cells = getSelectedCells(range, table);
5044					if (!cells || cells.length != 1)
5045						return false;
5046					cell = cells[0];
5047				}
5048
5049				if (!cell)
5050					return false;
5051
5052				var tr = cell.parentNode;
5053				BX.remove(cell);
5054
5055				if (tr.cells.length == 0)
5056					BX.remove(tr);
5057
5058				if (table.rows.length == 0)
5059					BX.remove(table);
5060			}
5061
5062			function removeSelectedCells(range, table)
5063			{
5064				var cells = getSelectedCells(range, table);
5065				if (!cells || cells.length == 1)
5066					return false;
5067
5068				var i, tr;
5069				for (i = 0; i < cells.length; i++)
5070				{
5071					tr = cells[i].parentNode;
5072					BX.remove(cells[i]);
5073
5074					if (tr.cells.length == 0)
5075						BX.remove(tr);
5076				}
5077
5078				if (table.rows.length == 0)
5079					BX.remove(table);
5080			}
5081
5082			return {
5083				exec: function(action, value)
5084				{
5085					var node = value.range.commonAncestorContainer;
5086
5087					switch (value.actionType)
5088					{
5089						// Insert
5090						case 'insertColumnLeft':
5091						case 'insertColumnRight':
5092							insertColumn(node, value.tableNode, value.actionType);
5093							break;
5094						case 'insertRowUpper':
5095						case 'insertRowLower':
5096							insertRow(node, value.tableNode, value.actionType);
5097							break;
5098						case 'insertCellLeft':
5099						case 'insertCellRight':
5100							insertCell(node, value.tableNode, value.actionType);
5101							break;
5102
5103						// Remove
5104						case 'removeColumn':
5105							removeColumn(value.range, value.tableNode);
5106							break;
5107						case 'removeRow':
5108							removeRow(value.range, value.tableNode);
5109							break;
5110						case 'removeCell':
5111							removeCell(value.range, value.tableNode);
5112							break;
5113						case 'removeSelectedCells':
5114							removeSelectedCells(value.range, value.tableNode);
5115							break;
5116
5117						// Merge
5118						case 'mergeSelectedCells':
5119							mergeCells(value.range, value.tableNode);
5120							break;
5121						case 'mergeRightCell':
5122							mergeRightCell(value.range, value.tableNode);
5123							break;
5124						case 'mergeBottomCell':
5125							mergeBottomCell(value.range, value.tableNode);
5126							break;
5127						case 'mergeRow':
5128							mergeRow(value.range, value.tableNode);
5129							break;
5130						case 'mergeColumn':
5131							mergeColumn(value.range, value.tableNode);
5132							break;
5133
5134						// split
5135						case 'splitHorizontally':
5136							splitHorizontally(value.range, value.tableNode);
5137							break;
5138						case 'splitVertically':
5139							splitVertically(value.range, value.tableNode);
5140							break;
5141					}
5142
5143				},
5144				state: BX.DoNothing,
5145				value: BX.DoNothing,
5146				getSelectedCells: getSelectedCells,
5147				canBeMerged: canBeMerged,
5148				canBeMergedWithRight: canBeMergedWithRight,
5149				canBeMergedWithBottom: canBeMergedWithBottom
5150			};
5151		},
5152
5153
5154		GetFormatBbCode: function()
5155		{
5156			var _this = this;
5157			return {
5158				view: 'textarea',
5159				exec: function(action, params)
5160				{
5161					var
5162						value = params.value,
5163						tag = params.tag.toUpperCase(),
5164						tag_end = tag;
5165
5166					if (tag == 'FONT' || tag == 'COLOR' || tag == 'SIZE')
5167					{
5168						tag += "=" + value;
5169					}
5170
5171					if(params.singleTag === true)
5172						_this.editor.textareaView.WrapWith("[", "]", tag);
5173					else
5174						_this.editor.textareaView.WrapWith("[" + tag + "]", "[/" + tag_end + "]");
5175				},
5176				state: BX.DoNothing,
5177				value: BX.DoNothing
5178			};
5179		}
5180	};
5181
5182	window.BXEditorActions = BXEditorActions;
5183})();

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.