| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238 | 
							- class ClassPage {
 
- 	static ModeCookieName = 'doczilla-js-docs-class-page-mode';
 
- 	static Mode = {
 
- 		Tabs: 'mode-tabs',
 
- 		List: 'mode-list'
 
- 	};
 
- 	static StyleClasses = {
 
- 		Selected: 'selected',
 
- 		Active: 'active',
 
- 		CtrlPressed: 'ctrl-pressed',
 
- 		ShiftPressed: 'shift-pressed',
 
- 		ParentsBranch: 'parents-branch',
 
- 		ClassItem: 'class-item',
 
- 		ClassName: 'class-name',
 
- 		ClassIcon: 'class-icon',
 
- 		Filler: 'filler',
 
- 		FullSourcePrompt: 'full-source-prompt',
 
- 		FullSourcePromptText: 'full-source-prompt-text',
 
- 		FullSourcePromptButton: 'full-source-prompt-button',
 
- 		Spacing: 'spacing',
 
- 		CmLink: 'cm-link',
 
- 		Z8Locale: 'z8-locale',
 
- 		Hidden: 'hidden',
 
- 		Collapsed: 'collapsed',
 
- 		Highlighted: 'highlighted',
 
- 		Readonly: 'readonly',
 
- 		Clickable: 'clickable',
 
- 		Empty: 'empty',
 
- 		White: 'white'
 
- 	};
 
- 	static Attributes = {
 
- 		DataTab: 'data-tab',
 
- 		DataDisplayMode: 'data-display-mode',
 
- 		DataClassName: 'data-class-name',
 
- 		OnClick: 'onclick',
 
- 		DataPropertyName: 'data-property-name',
 
- 		DataPropertyType: 'data-property-type',
 
- 		DataPropertyParent: 'data-property-parent',
 
- 		DataPropertyInherited: 'data-property-inherited',
 
- 		DataPropertyDynamic: 'data-property-dynamic'
 
- 	};
 
- 	static Messages = {
 
- 		NoMixins: 'This class has no mixins.',
 
- 		NoChildren: 'This class has no child classes.',
 
- 		NoMixedIn: 'This class is not mixed in any classes.',
 
- 		NoParents: 'This is a base class, which has no parent classes.',
 
- 		ShowFullSourceText: 'There were found another entities in the source file of this class. Would you like to see full source file?',
 
- 		HideFullSourceText: 'Full source file shown. Would you like to hide all entities except the target class?',
 
- 		PromptButtonText: 'OK',
 
- 		CmLinkTipPrefix: 'Ctrl+Click to go to class'
 
- 	};
 
- 	static TabNames = {
 
- 		Editor: 'Editor',
 
- 		Methods: 'Methods',
 
- 		Parents: 'Parents',
 
- 		Properties: 'Properties',
 
- 		Mixins: 'Mixins',
 
- 		Children: 'Children',
 
- 		MixedIn: 'MixedIn',
 
- 		Contribution: 'Contribution'
 
- 	};
 
- 	static PropertyType = {
 
- 		Statics: 'statics',
 
- 		Base: 'base',
 
- 		Overridden: 'overridden',
 
- 		Dynamic: 'dynamic',
 
- 		Inherited: 'inherited'
 
- 	};
 
- 	static PropertyLabel = {
 
- 		Statics: 'Static properties',
 
- 		Base: 'Base properties',
 
- 		Overridden: 'Overridden properties',
 
- 		Dynamic: 'Dynamic properties',
 
- 		Inherited: 'Inherited properties'
 
- 	};
 
- 	static MethodLabel = {
 
- 		Statics: 'Static methods',
 
- 		Base: 'Base methods',
 
- 		Overridden: 'Overridden methods',
 
- 		Dynamic: 'Dynamic methods',
 
- 		Inherited: 'Inherited methods'
 
- 	};
 
- 	static ClassProperties = {
 
- 		Name: 'name',
 
- 		Methods: 'methods',
 
- 		Properties: 'properties',
 
- 		Children: 'children',
 
- 		Mixins: 'mixins',
 
- 		MixedIn: 'mixedIn',
 
- 		ParentsBranch: 'parentsBranch',
 
- 		Statics: 'statics',
 
- 		DynamicProperties: 'dynamicProperties',
 
- 		Root: 'root',
 
- 		ShortName: 'shortName'
 
- 	};
 
- 	static ContextMenuType = {
 
- 		PropertyItem: 'property-item'
 
- 	};
 
- 	static __static__ = '__static__';
 
- 	start() {
 
- 		if(typeof Class === 'string') {
 
- 			return this;
 
- 		}
 
- 		this.tabElements = {
 
- 			[ClassPage.TabNames.Editor]:				DOM.get('.tab.editor'),
 
- 			[ClassPage.TabNames.Methods]:				DOM.get('.tab.methods'),
 
- 			[ClassPage.TabNames.Parents]:				DOM.get('.tab.parents'),
 
- 			[ClassPage.TabNames.Properties]:		DOM.get('.tab.properties'),
 
- 			[ClassPage.TabNames.Mixins]:				DOM.get('.tab.mixins'),
 
- 			[ClassPage.TabNames.Children]:			DOM.get('.tab.children'),
 
- 			[ClassPage.TabNames.MixedIn]:				DOM.get('.tab.mixedin')
 
- 		};
 
- 		
 
- 		this.contentElements = {
 
- 			[ClassPage.TabNames.Editor]:				DOM.get('.content-tab#editor'),
 
- 			[ClassPage.TabNames.Methods]:				DOM.get('.content-tab#methods'),
 
- 			[ClassPage.TabNames.Parents]:				DOM.get('.content-tab#parents'),
 
- 			[ClassPage.TabNames.Properties]:		DOM.get('.content-tab#properties'),
 
- 			[ClassPage.TabNames.Mixins]:				DOM.get('.content-tab#mixins'),
 
- 			[ClassPage.TabNames.Children]:			DOM.get('.content-tab#children'),
 
- 			[ClassPage.TabNames.MixedIn]:				DOM.get('.content-tab#mixedin')
 
- 		};
 
- 		if(isEditor) {
 
- 			this.tabElements[ClassPage.TabNames.Contribution] = DOM.get('.tab.contribution');
 
- 			this.contentElements[ClassPage.TabNames.Contribution] = DOM.get('.content-tab#contribution');
 
- 		}
 
- 		this.documented = Object.keys(Comments).filter((key) => { return Comments[key].text.length > 0; }).length;
 
- 		this.documentable = 0;
 
- 		this.inheritedCommentsQuery = {};
 
- 		this.inheritedCommentsFields = {};
 
- 		this.propertyItemElements = {};
 
- 		this.documentedPercentage = DOM.get('.class-documented-percentage');
 
- 		const rightContainer = this.rightContainer = DOM.get('.right');
 
- 		const modeCookieValue = DOM.getCookieProperty(App.CookieName, ClassPage.ModeCookieName);
 
- 		
 
- 		if(!modeCookieValue)
 
- 			DOM.setCookieProperty(App.CookieName, ClassPage.ModeCookieName, ClassPage.Mode.Tabs);
 
- 		const mode = this.mode = modeCookieValue || ClassPage.Mode.Tabs;
 
- 		const tabsModeButton = this.tabsModeButton = DOM.get('.display-mode-button.mode-tabs');
 
- 		const listModeButton = this.listModeButton = DOM.get('.display-mode-button.mode-list');
 
- 		/* >>> Context menu */
 
- 		this.contextMenu = DOM.get('.context-menu');
 
- 		this.contextMenuItems = {};
 
- 		/* <<< Context menu */
 
- 		(mode === ClassPage.Mode.Tabs ? tabsModeButton : listModeButton).addClass(ClassPage.StyleClasses.Selected);
 
- 		rightContainer.addClass(mode);
 
- 		this.renderContent();
 
- 		this.markContentInEditor();
 
- 		this.registerEventListeners();
 
- 		this.applyHash();
 
- 		this.openSocket();
 
- 		return this;
 
- 	}
 
- 	switchMode(mode) {
 
- 		this.rightContainer.removeClass(this.mode);
 
- 		this.mode = mode;
 
- 		DOM.setCookieProperty(App.CookieName, ClassPage.ModeCookieName, mode);
 
- 		this.rightContainer.addClass(mode);
 
- 		this.codeMirrorEditor.cmRefresh();
 
- 	}
 
- 	selectTab(tab) {
 
- 		tab = typeof tab === 'string' ? this.tabElements[tab] : tab;
 
- 		const selectedTab = this.selectedTab;
 
- 		let filler = selectedTab ? selectedTab.getFirstChild('.filler') : null;
 
- 		if(!filler)
 
- 			filler = DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.Filler });
 
- 		selectedTab && selectedTab.removeClass(ClassPage.StyleClasses.Selected);
 
- 		this.selectedTab = tab.addClass(ClassPage.StyleClasses.Selected);
 
- 		this.selectedTab.append(filler);
 
- 	}
 
- 	activateContent(content) {
 
- 		content = typeof content === 'string' ? this.contentElements[content] : content;
 
- 		if(this.activeContent)
 
- 			this.activeContent.removeClass(ClassPage.StyleClasses.Active);
 
- 		this.activeContent = content.addClass(ClassPage.StyleClasses.Active);
 
- 		if(content === this.contentElements[ClassPage.TabNames.Editor])
 
- 			this.codeMirrorEditor.cmRefresh();
 
- 		if(content === this.contentElements[ClassPage.TabNames.Properties] || content === this.contentElements[ClassPage.TabNames.Methods])
 
- 			DOM.getAll('.property-item-comment-input').forEach((item) => {
 
- 				item.style('height', `${Math.min(422, item.get().scrollHeight + 2)}px`);
 
- 			});
 
- 	}
 
- 	registerEventListeners() {
 
- 		this.registerTabsEventListeners();
 
- 		this.registerModeButtonsEventListeners();
 
- 	}
 
- 	registerTabsEventListeners() {
 
- 		const tabElements = this.tabElements;
 
- 		for(const tabName of Object.keys(tabElements)) {
 
- 			tabElements[tabName].on(DOM.Events.Click, this.onTabClick.bind(this));
 
- 		}
 
- 	}
 
- 	registerModeButtonsEventListeners() {
 
- 		this.tabsModeButton.on(DOM.Events.Click, this.onModeButtonClick.bind(this));
 
- 		this.listModeButton.on(DOM.Events.Click, this.onModeButtonClick.bind(this));
 
- 	}
 
- 	prepareSource() {
 
- 		const className = Class[ClassPage.ClassProperties.Name].replaceAll('.', '\\.');
 
- 		const classRx = new RegExp(`Z8\\.define\\(\'${className}\',\\s*\\{(?:.|[\r\n])+?^\\}\\);?`, 'gm');
 
- 		const classSourceMatch = ClassSource.match(classRx);
 
- 		if(!classSourceMatch) { // remove after `}\n);` issue fixed...
 
- 			this.sourceHasAnotherEntities = false;
 
- 			return this.classSource = ClassSource;
 
- 		}
 
- 		const classSource = this.classSource = classSourceMatch[0];
 
- 		this.sourceHasAnotherEntities = ClassSource.trim() !== classSource;
 
- 		return classSource;
 
- 	}
 
- 	renderContent() {
 
- 		this.renderEditor();
 
- 		this.renderParents();
 
- 		this.renderMixins();
 
- 		this.renderChildren();
 
- 		this.renderMixedIn();
 
- 		this.renderProperties();
 
- 		this.renderMethods();
 
- 		this.renderDocumentedPercentage();
 
- 		this.loadInheritedComments();
 
- 		if(isEditor)
 
- 			this.renderContribution();
 
- 	}
 
- 	renderDocumentedPercentage() {
 
- 		this.documentedPercentage.setInnerHTML(`${Math.round(this.documented/this.documentable * 10000) / 100}%`);
 
- 	}
 
- 	renderEditor() {
 
- 		this.codeMirrorEditor = CodeMirror(DOM.get('#editor').get(), {
 
- 			[App.CodeMirrorProperties.Value]:						this.prepareSource(ClassSource),
 
- 			[App.CodeMirrorProperties.Mode]:						'javascript',
 
- 			[App.CodeMirrorProperties.Theme]:						'darcula',
 
- 			[App.CodeMirrorProperties.Readonly]:				true,
 
- 			[App.CodeMirrorProperties.LineNumbers]:			true,
 
- 			[App.CodeMirrorProperties.MatchBrackets]:		true,
 
- 			[App.CodeMirrorProperties.ScrollbarStyle]:	'overlay',
 
- 			[App.CodeMirrorProperties.ConfigureMouse]: (cm, repeat, ev) => { 
 
- 				return { 'addNew': false };
 
- 			},
 
- 		});
 
- 		if(this.sourceHasAnotherEntities)
 
- 			this.renderFullSourcePrompt();
 
- 		this.codeMirrorEditorElement = DOM.get('.CodeMirror');
 
- 	}
 
- 	renderProperties() {
 
- 		const propertiesElement = this.contentElements[ClassPage.TabNames.Properties];
 
- 		const properties = this.getProperties(false);
 
- 		
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Statics,			ClassPage.PropertyLabel.Statics, propertiesElement, false, false);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Base,				ClassPage.PropertyLabel.Base, propertiesElement, false, false);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Overridden,	ClassPage.PropertyLabel.Overridden, propertiesElement, false, false);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Dynamic,			ClassPage.PropertyLabel.Dynamic, propertiesElement, false, false);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Inherited,		ClassPage.PropertyLabel.Inherited, propertiesElement, true, false);
 
- 	}
 
- 	renderMethods() {
 
- 		const methodsElement = this.contentElements[ClassPage.TabNames.Methods];
 
- 		const properties = this.getProperties(true);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Statics,			ClassPage.MethodLabel.Statics, methodsElement, false, true);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Base,				ClassPage.MethodLabel.Base, methodsElement, false, true);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Overridden,	ClassPage.MethodLabel.Overridden, methodsElement, false, true);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Dynamic,			ClassPage.MethodLabel.Dynamic, methodsElement, false, true);
 
- 		this.renderPropertiesType(properties, ClassPage.PropertyType.Inherited,		ClassPage.MethodLabel.Inherited, methodsElement, true, true);
 
- 	}
 
- 	// TODO: refactor! Make PropertyItem class
 
- 	renderPropertiesType(properties, type, headerText, container, initiallyHidden, isMethods) {
 
- 		const isStatics = type === ClassPage.PropertyType.Statics;
 
- 		const isInherited = type === ClassPage.PropertyType.Inherited;
 
- 		properties = properties[type];
 
- 		if(!properties || properties.length == 0)
 
- 			return;
 
- 		const propertyItemClickable = (element) => {
 
- 			let el = element;
 
- 			while(!el.hasClass('property-item-comment-static') && !el.hasClass('property-item'))
 
- 				el = el.getParent();
 
- 			return element.hasClass('property-item-nearest-parent-span')
 
- 					|| element.hasClass('property-item-comment-input')
 
- 					|| element.hasClass('property-item-comment-button')
 
- 					|| element.hasClass('property-item-saving-filler')
 
- 					|| (el.hasClass('property-item-comment-static') && el.hasClass(ClassPage.StyleClasses.Clickable))
 
- 					|| element.getTag() === DOM.Tags.A;
 
- 		};
 
- 		if(!isInherited) {
 
- 			this.documentable += properties.length;
 
- 		} else {
 
- 			const inheritedCommentsQuery = this.inheritedCommentsQuery;
 
- 			properties.forEach((prop) => {
 
- 				if(inheritedCommentsQuery[prop.nearestParent]) {
 
- 					inheritedCommentsQuery[prop.nearestParent].properties.push(prop.key);
 
- 				} else {
 
- 					inheritedCommentsQuery[prop.nearestParent] = { root: prop.nearestParentRoot, className: prop.nearestParent, properties: [prop.key] };
 
- 				}
 
- 			});
 
- 		}
 
- 		const propertiesHeaderText = DOM.create({ tag: DOM.Tags.Span, cls: 'properties-header-text', innerHTML: headerText });
 
- 		const propertiesHeaderCollapsedIcon = DOM.create({ tag: DOM.Tags.Div, cls: 'properties-header-collapsed-icon' });
 
- 		const propertiesHeader = DOM.create({ tag: DOM.Tags.Div, cls: 'properties-header', cn: [propertiesHeaderText, propertiesHeaderCollapsedIcon] }, container);
 
- 		const propertiesList = DOM.create({ tag: DOM.Tags.Div, cls: 'properties-list' }, container);
 
- 		
 
- 		if(initiallyHidden) {
 
- 			propertiesList.addClass(ClassPage.StyleClasses.Hidden);
 
- 			propertiesHeader.addClass(ClassPage.StyleClasses.Collapsed);
 
- 		}
 
- 		for(const property of properties) {
 
- 			const key = isStatics ? `${ClassPage.__static__}${property.key}` : property.key;
 
- 			const propertyItem = DOM.create({ tag: DOM.Tags.Div, cls: 'property-item', attr: { [ClassPage.Attributes.DataPropertyName]: key, [ClassPage.Attributes.DataPropertyType]: property.type }}, propertiesList).on(DOM.Events.MouseDown, (e) => {
 
- 				const element = CDElement.get(e.target);
 
- 				if(e.buttons === DOM.MouseButtons.Right) {
 
- 					if(element.hasClass('property-item-saving-filler'))
 
- 						return;
 
- 					setTimeout(() => {
 
- 						this.showContextMenu(ClassPage.ContextMenuType.PropertyItem, element, { x: e.pageX, y: e.pageY });
 
- 					}, 10);
 
- 				} else if (e.buttons === DOM.MouseButtons.Left) {					
 
- 					if(propertyItemClickable(element))
 
- 						return;
 
- 					if(type === ClassPage.PropertyType.Inherited) {
 
- 						Url.goTo(`/class/${property.nearestParent}#${isMethods ? 'Methods' : 'Properties'}:${key}`);
 
- 					} else {
 
- 						this.searchPropertyInEditor(isMethods, property.dynamic, key);
 
- 					}
 
- 				}
 
- 			});
 
- 			const itemNameText = DOM.create({ tag: DOM.Tags.Span, innerHTML: isMethods ? 'Signature: ' : 'Name: ' });
 
- 			const itemNameValue = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-name-span', innerHTML: isMethods ? property.value : property.key });
 
- 			DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-name', cn: [itemNameText, itemNameValue] }, propertyItem);
 
- 			
 
- 			if(type !== ClassPage.PropertyType.Dynamic && !isMethods) {
 
- 				const itemTypeText = DOM.create({ tag: DOM.Tags.Span, innerHTML: 'Type: ' });
 
- 				const itemTypeValue = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-type-span', innerHTML: property.type });
 
- 				DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-type', cn: [itemTypeText, itemTypeValue] }, propertyItem);
 
- 			}
 
- 			if(type !== ClassPage.PropertyType.Dynamic && !isMethods && property.type !== 'undefined') {
 
- 				const itemValueText = DOM.create({ tag: DOM.Tags.Span, innerHTML: 'Default value: ' });
 
- 				const itemValueValue = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-default-value-span', innerHTML: property.type === 'string' ? `'${property.value}'` : property.value });
 
- 				DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-default-value', cn: [itemValueText, itemValueValue] }, propertyItem);
 
- 			}
 
- 			if(type !== ClassPage.PropertyType.Dynamic && type !== ClassPage.PropertyType.Statics && type !== ClassPage.PropertyType.Base) {
 
- 				const itemParentText = DOM.create({ tag: DOM.Tags.Span, innerHTML: 'Nearest parent: ' });
 
- 				const itemParentValue = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-nearest-parent-span', innerHTML: property.nearestParent }).on('click', (e) => {
 
- 					Url.goTo(`/class/${property.nearestParent}`);
 
- 				});
 
- 				DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-nearest-parent', cn: [itemParentText, itemParentValue] }, propertyItem);
 
- 				propertyItem.setAttribute(ClassPage.Attributes.DataPropertyParent, property.nearestParent);
 
- 			}
 
- 			if(type === ClassPage.PropertyType.Dynamic) {
 
- 				propertyItem.setAttribute(ClassPage.Attributes.DataPropertyDynamic, 'true');
 
- 			}
 
- 			const itemCommentText = DOM.create({ tag: DOM.Tags.Div, innerHTML: 'Comment:' });
 
- 			const itemCommentCn = [itemCommentText];
 
- 			const loadedComment = Comments[type === ClassPage.PropertyType.Statics ? `${ClassPage.__static__}${property.key}` : property.key];
 
- 			const loadedCommentText = loadedComment && typeof loadedComment === 'object' ? loadedComment.text : '';
 
- 			const hasComment = loadedComment && typeof loadedComment === 'object' && loadedCommentText.length > 0;
 
- 			const itemCommentStatic = DOM.create({ tag: DOM.Tags.Div, cls: `property-item-comment-static${!hasComment ? ' empty' : ''}`, innerHTML: hasComment ? CDUtils.nl2br(loadedCommentText) : 'Not commented yet...' });
 
- 			itemCommentCn.push(itemCommentStatic);
 
- 			if(type === ClassPage.PropertyType.Inherited) {
 
- 				this.inheritedCommentsFields[`${property.nearestParent}:${property.key}`] = itemCommentStatic;
 
- 				propertyItem.setAttribute(ClassPage.Attributes.DataPropertyInherited, 'true');
 
- 			}
 
- 			this.propertyItemElements[type === ClassPage.PropertyType.Statics ? `${ClassPage.__static__}${property.key}` : property.key] = propertyItem;
 
- 			
 
- 			if(isEditor) {
 
- 				const itemCommentInput = DOM.create({ tag: DOM.Tags.Textarea, cls: 'property-item-comment-input hidden', attr: { 'placeholder': 'Not commented yet...'} }).setValue(CDUtils.br2nl(loadedCommentText));
 
- 				itemCommentCn.push(itemCommentInput);
 
- 				
 
- 				if(type === ClassPage.PropertyType.Inherited) {
 
- 					itemCommentInput.addClass(ClassPage.StyleClasses.Readonly).setAttribute('readonly', 'true');
 
- 				} else {
 
- 					itemCommentStatic.addClass(ClassPage.StyleClasses.Clickable);
 
- 					itemCommentInput
 
- 						.on(DOM.Events.KeyDown, this.delayedAdjustCommentInputHeight.bind(this))
 
- 						.on(DOM.Events.Change, this.adjustCommentInputHeight.bind(this))
 
- 						.on(DOM.Events.Cut, this.delayedAdjustCommentInputHeight.bind(this))
 
- 						.on(DOM.Events.Paste, this.delayedAdjustCommentInputHeight.bind(this))
 
- 						.on(DOM.Events.Drop, this.delayedAdjustCommentInputHeight.bind(this));
 
- 					const onCommentSave = (e) => {
 
- 						const commentContent = itemCommentInput.getValue();
 
- 						if(commentContent === CDUtils.br2nl(itemCommentStatic.getValue()) || commentContent === '' && itemCommentStatic.hasClass('empty'))
 
- 							return;
 
- 						const propertyName = `${type === ClassPage.PropertyType.Statics ? ClassPage.__static__ : ''}${property.key}`;
 
- 						const className = Class[ClassPage.ClassProperties.Name];
 
- 						const classRoot = Class[ClassPage.ClassProperties.Root];
 
- 						propertyItem.addClass('saving');
 
- 						itemCommentInput.blur();
 
- 						fetch('/updateComment', {
 
- 							method: 'POST',
 
- 							headers: {
 
- 								'Content-Type': 'application/x-www-form-urlencoded'
 
- 							},
 
- 							body: new URLSearchParams({
 
- 								'root': classRoot,
 
- 								'class': className,
 
- 								'property': propertyName,
 
- 								'comment': commentContent
 
- 							})
 
- 						}).then((res) => {
 
- 							if(res.status !== 202) {
 
- 								propertyItem.removeClass('saving');
 
- 								console.error(`Comment update failed (${res.status})`);
 
- 							}
 
- 						}).catch((e) => {
 
- 								propertyItem.removeClass('saving');
 
- 								console.error(`Comment update failed`);
 
- 						});
 
- 					};
 
- 					const itemCommentOkButton = DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-comment-button hidden', innerHTML: 'OK' }).on(DOM.Events.Click, onCommentSave);
 
- 					itemCommentCn.push(itemCommentOkButton);
 
- 					itemCommentInput.on(DOM.Events.KeyDown, (e) => {
 
- 						if(e.key === DOM.Keys.Escape) {
 
- 							const inputScrollTop = itemCommentInput.get().scrollTop;
 
- 							itemCommentInput.switchClass(ClassPage.StyleClasses.Hidden);
 
- 							itemCommentOkButton.switchClass(ClassPage.StyleClasses.Hidden);
 
- 							itemCommentStatic.switchClass(ClassPage.StyleClasses.Hidden);
 
- 							itemCommentStatic.get().scrollTop = inputScrollTop;
 
- 							if(!itemCommentStatic.hasClass('empty'))
 
- 								itemCommentInput.setValue(CDUtils.br2nl(itemCommentStatic.getValue()));
 
- 						}
 
- 						if(e.key === DOM.Keys.Enter && !e.shiftKey) {
 
- 							onCommentSave();
 
- 							e.preventDefault();
 
- 						}
 
- 					});
 
- 					itemCommentStatic.on(DOM.Events.Click, (e) => {
 
- 						if(CDElement.get(e.target).getTag() === DOM.Tags.A)
 
- 							return;
 
- 						itemCommentInput.switchClass(ClassPage.StyleClasses.Hidden);
 
- 						itemCommentInput.focus();
 
- 						itemCommentInput.style('height', `${Math.min(422, itemCommentStatic.get().scrollHeight + 2)}px`);
 
- 						itemCommentInput.get().scrollTop = itemCommentStatic.get().scrollTop;
 
- 						itemCommentOkButton.switchClass(ClassPage.StyleClasses.Hidden);
 
- 						itemCommentStatic.switchClass(ClassPage.StyleClasses.Hidden);
 
- 					});
 
- 				}
 
- 				DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-saving-filler' }, propertyItem);
 
- 			}
 
- 			if(hasComment)
 
- 				itemCommentCn.push(this.createCommentDateElement(loadedComment.timestamp, loadedComment.author));
 
- 			DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-comment', cn: itemCommentCn }, propertyItem);
 
- 		}
 
- 		propertiesHeader.on(DOM.Events.Click, (e) => {
 
- 			propertiesList.switchClass(ClassPage.StyleClasses.Hidden);
 
- 			propertiesHeader.switchClass(ClassPage.StyleClasses.Collapsed);
 
- 		});
 
- 	}
 
- 	createCommentDateElement(date, author) {
 
- 		const commentDateText = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-comment-date-text', innerHTML: `Commented by <b>${author}</b> on: ` });
 
- 		const commentDateDate = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-comment-date-date', innerHTML: CDUtils.dateFormatUTC(date, 3, 'D.M.Y, H:I:S') });
 
- 		return DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-comment-date', cn: [commentDateText, commentDateDate] });
 
- 	}
 
- 	loadInheritedComments() {
 
- 		if(Object.keys(this.inheritedCommentsQuery).length === 0 || Object.keys(this.inheritedCommentsFields).length === 0)
 
- 			return;
 
- 		fetch('/getInheritedComments', {
 
- 			method: 'POST',
 
- 			headers: {
 
- 				'Content-Type': 'application/x-www-form-urlencoded'
 
- 			},
 
- 			body: new URLSearchParams({
 
- 				query: JSON.stringify(this.inheritedCommentsQuery)
 
- 			})
 
- 		}).then(res => res.json()).then((inheritedComments) => {
 
- 			for(const cls of Object.keys(inheritedComments)) {
 
- 				const props = inheritedComments[cls];
 
- 				for(const prop of Object.keys(props)) {
 
- 					const element = this.inheritedCommentsFields[`${cls}:${prop}`];
 
- 					if(element) {
 
- 						element.setInnerHTML(props[prop].text);
 
- 						element.removeClass(ClassPage.StyleClasses.Empty);
 
- 						element.getParent().append(this.createCommentDateElement(props[prop].timestamp, props[prop].author));
 
- 					}
 
- 				}
 
- 			}
 
- 		});
 
- 	}
 
- 	getProperties(methods) {
 
- 		const filter = methods ? (item) => item.type === 'method' : (item) => item.type !== 'method';
 
- 		const statics = Class[ClassPage.ClassProperties.Statics].filter(filter);
 
- 		const properties = Class[ClassPage.ClassProperties.Properties].sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase())).sort((a, b) => {
 
- 			return Class[ClassPage.ClassProperties.ParentsBranch].indexOf(a.nearestParent) > Class[ClassPage.ClassProperties.ParentsBranch].indexOf(b.nearestParent) ? -1 : 1;
 
- 		}).filter(filter);
 
- 		const dynamicProperties = methods ? [] : Class[ClassPage.ClassProperties.DynamicProperties];
 
- 		const result = {
 
- 			[ClassPage.PropertyType.Statics]: statics,
 
- 			[ClassPage.PropertyType.Base]: properties.filter((item) => !item.inherited),
 
- 			[ClassPage.PropertyType.Overridden]: properties.filter((item) => item.overridden),
 
- 			[ClassPage.PropertyType.Inherited]: properties.filter((item) => item.inherited && !item.overridden),
 
- 			[ClassPage.PropertyType.Dynamic]: dynamicProperties
 
- 		};
 
- 		return result;
 
- 	}
 
- 	renderMixins() {
 
- 		const mixinsElement = this.contentElements[ClassPage.TabNames.Mixins];
 
- 		if(Class[ClassPage.ClassProperties.Mixins].length == 0) {
 
- 			DOM.create({ tag: DOM.Tags.Div, style: 'font-size: 24px;', innerHTML: ClassPage.Messages.NoMixins }, mixinsElement);
 
- 			mixinsElement.addClass(ClassPage.StyleClasses.Empty);
 
- 			return;
 
- 		}
 
- 		this.renderClassItems(Class[ClassPage.ClassProperties.Mixins], mixinsElement);
 
- 	}
 
- 	renderChildren() {
 
- 		const childrenElement = this.contentElements[ClassPage.TabNames.Children];
 
- 		if(Class[ClassPage.ClassProperties.Children].length == 0) {
 
- 			DOM.create({ tag: DOM.Tags.Div, style: 'font-size: 24px;', innerHTML: ClassPage.Messages.NoChildren }, childrenElement);
 
- 			childrenElement.addClass(ClassPage.StyleClasses.Empty);
 
- 			return;
 
- 		}
 
- 		this.renderClassItems(Class[ClassPage.ClassProperties.Children], childrenElement);
 
- 	}
 
- 	renderMixedIn() {
 
- 		const mixedInElement = this.contentElements[ClassPage.TabNames.MixedIn];
 
- 		if(Class[ClassPage.ClassProperties.MixedIn].length == 0) {
 
- 			DOM.create({ tag: DOM.Tags.Div, style: 'font-size: 24px;', innerHTML: ClassPage.Messages.NoMixedIn }, mixedInElement);
 
- 			mixedInElement.addClass(ClassPage.StyleClasses.Empty);
 
- 			return;
 
- 		}
 
- 		this.renderClassItems(Class[ClassPage.ClassProperties.MixedIn], mixedInElement);
 
- 	}
 
- 	renderFullSourcePrompt() {
 
- 		const editorContent = this.contentElements[ClassPage.TabNames.Editor];
 
- 		const prompt = DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.FullSourcePrompt }, editorContent);
 
- 		const text = this.fullSourcePromptText = DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.FullSourcePromptText, innerHTML: ClassPage.Messages.ShowFullSourceText }, prompt);
 
- 		const button = DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.FullSourcePromptButton, innerHTML: ClassPage.Messages.PromptButtonText }, prompt);
 
- 		const onButtonClick = (e) => {
 
- 			this.switchFullSource();
 
- 		};
 
- 		button.on(DOM.Events.Click, onButtonClick.bind(this));
 
- 	}
 
- 	switchFullSource(show) {
 
- 		const shown = this.fullSourceCodeShown = show !== undefined ? !show : !this.fullSourceCodeShown;
 
- 		this.codeMirrorEditor.cmSetValue(shown ? ClassSource : this.classSource);
 
- 		if(shown)
 
- 			this.findAndScrollToTargetClass();
 
- 		this.codeMirrorEditor.cmRefresh();
 
- 		this.markContentInEditor();
 
- 		this.fullSourcePromptText && this.fullSourcePromptText.setInnerHTML(shown ? ClassPage.Messages.HideFullSourceText : ClassPage.Messages.ShowFullSourceText);
 
- 		return shown;
 
- 	}
 
- 	markContentInEditor() {
 
- 		const staticsRange = this.getStaticsRange();
 
- 		this.codeMirrorEditor.cmEachLine((lineHandle) => {
 
- 			this.markExtend(lineHandle);
 
- 			this.markMixins(lineHandle);
 
- 			this.markZ8Locales(lineHandle);
 
- 			this.markNew(lineHandle);
 
- 			this.markThis(lineHandle);
 
- 			this.markProperties(lineHandle, staticsRange);
 
- 		});
 
- 	}
 
- 	findAndScrollToTargetClass() {
 
- 		const className = Class[ClassPage.ClassProperties.Name].replaceAll('.', '\\.');
 
- 		const editor = this.codeMirrorEditor;
 
- 		const defineRx = new RegExp(`Z8\\.define\\(\'${className}\',`);
 
- 		editor.cmEachLine((lineHandle) => {
 
- 			const text = lineHandle.text;
 
- 			const match = text.match(defineRx);
 
- 			if(match) {
 
- 				editor.scrollIntoView({ line: lineHandle.lineNo(), ch: 0 }, 100);
 
- 				return;
 
- 			}
 
- 		});
 
- 	}
 
- 	renderParents() {
 
- 		const parentsContent = this.contentElements[ClassPage.TabNames.Parents];
 
- 		const parentsContainer = DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.ParentsBranch }, parentsContent);
 
- 		if(Class[ClassPage.ClassProperties.ParentsBranch].length == 0) {
 
- 			DOM.create({ tag: DOM.Tags.Div, style: 'font-size: 24px;', innerHTML: ClassPage.Messages.NoParents }, parentsContent);
 
- 			parentsContent.addClass(ClassPage.StyleClasses.Empty);
 
- 			return;
 
- 		}
 
- 		this.renderClassItems(Class[ClassPage.ClassProperties.ParentsBranch], parentsContainer, true);
 
- 	}
 
- 	renderClassItems(itemsList, container, withIndent) {
 
- 		let indent = 0;
 
- 		if(!withIndent)
 
- 			itemsList = itemsList.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
 
- 		for(const cls of itemsList) {
 
- 			const icon = DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.ClassIcon });
 
- 			const name = DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.ClassName, innerHTML: cls });
 
- 			const cn = [icon, name];
 
- 			if(indent > 0 && withIndent)
 
- 				cn.unshift(DOM.create({ tag: DOM.Tags.Div, cls: ClassPage.StyleClasses.Spacing, style: `width: ${10 * indent}px;` }));
 
- 			DOM.create({ tag: DOM.Tags.Div, cls: `${ClassPage.StyleClasses.ClassItem}${ withIndent ? ' indent' : '' }`, attr: { [ClassPage.Attributes.DataClassName]: cls }, cn: cn }, container).on(DOM.Events.Click, this.onClassClick.bind(this));
 
- 			indent++;
 
- 		}
 
- 	}
 
- 	markExtend(lineHandle) {
 
- 		const editor = this.codeMirrorEditor;
 
- 		const text = lineHandle.text;
 
- 		const match = text.match(/extend:\s*['"]?([\w\.]+)['"]?/);
 
- 		if (match) {
 
- 			const className = match[1];
 
- 			const from = { line: lineHandle.lineNo(), ch: match.index + 8 };
 
- 			const to = { line: lineHandle.lineNo(), ch: match.index + match[0].length };
 
- 			editor.markText(from, to, {
 
- 				className: ClassPage.StyleClasses.CmLink,
 
- 				title: `${ClassPage.Messages.CmLinkTipPrefix} ${className}`,
 
- 				attributes: {
 
- 					[ClassPage.Attributes.DataClassName]: className,
 
- 					[ClassPage.Attributes.OnClick]: 'window.page.onClassLinkClick(this);'
 
- 				}
 
- 			});
 
- 		}
 
- 	}
 
- 	markMixins(lineHandle) {
 
- 		const editor = this.codeMirrorEditor;
 
- 		const text = lineHandle.text;
 
- 		const match = text.match(/mixins:\s*(\[.*?\]|\w+)/);
 
- 		if (match) {
 
- 			let mixins = match[1].replace(/\[|\]/g, "").split(/\s*,\s*/);
 
- 			const mixinsStr = match[1].replace(/\[|\]/g, "");
 
- 			mixins = mixinsStr.split(/\s*,\s*/);
 
- 			const startIndex = match.index + match[0].indexOf(mixinsStr);
 
- 			for (var i = 0; i < mixins.length; i++) {
 
- 				const className = mixins[i].trim().replace(/^['"]|['"]$/g, "");
 
- 				const classIndex = mixinsStr.indexOf(className);
 
- 				const from = { line: lineHandle.lineNo(), ch: startIndex + classIndex };
 
- 				const to = { line: lineHandle.lineNo(), ch: startIndex + classIndex + className.length };
 
- 				editor.markText(from, to, {
 
- 					className: ClassPage.StyleClasses.CmLink,
 
- 					title: `${ClassPage.Messages.CmLinkTipPrefix} ${className}`,
 
- 					attributes: {
 
- 						[ClassPage.Attributes.DataClassName]: className,
 
- 						[ClassPage.Attributes.OnClick]: 'window.page.onClassLinkClick(this);'
 
- 					}
 
- 				});
 
- 			}
 
- 		}
 
- 	}
 
- 	markZ8Locales(lineHandle) {
 
- 		const editor = this.codeMirrorEditor;
 
- 		const text = lineHandle.text;
 
- 		const regexp = /Z8\.\$\('([\S]+)'(?:\s*,\s*.*)?\)/g;
 
- 		let match;
 
- 		while ((match = regexp.exec(text)) !== null) {
 
- 			const messageId = match[1];
 
- 			const from = { line: lineHandle.lineNo(), ch: match.index };
 
- 			const to = { line: lineHandle.lineNo(), ch: match.index + match[0].length };
 
- 			editor.markText(from, to, {
 
- 				className: ClassPage.StyleClasses.Z8Locale,
 
- 				title: `RU: ${Z8Locales['ru'][messageId]}\nEN: ${Z8Locales['en'][messageId]}`
 
- 			});
 
- 		}
 
- 	}
 
- 	markNew(lineHandle) {
 
- 		const editor = this.codeMirrorEditor;
 
- 		const text = lineHandle.text;
 
- 		const regexp = /new\s+([\w\.]+)/g;
 
- 		let match;
 
- 		while ((match = regexp.exec(text)) !== null) {
 
- 			const className = match[1];
 
- 			const from = { line: lineHandle.lineNo(), ch: match.index + 4 };
 
- 			const to = { line: lineHandle.lineNo(), ch: match.index + match[0].length };
 
- 			
 
- 			if(!ClassList[className] && !this.shortNameExists(className))
 
- 				continue;
 
- 			
 
- 			editor.markText(from, to, {
 
- 				className: ClassPage.StyleClasses.CmLink,
 
- 				title: `${ClassPage.Messages.CmLinkTipPrefix} ${className}`,
 
- 				attributes: {
 
- 					[ClassPage.Attributes.DataClassName]: className,
 
- 					[ClassPage.Attributes.OnClick]: 'window.page.onClassLinkClick(this);'
 
- 				}
 
- 			});
 
- 		}
 
- 	}
 
- 	markThis(lineHandle) {
 
- 		const editor = this.codeMirrorEditor;
 
- 		const text = lineHandle.text;
 
- 		const regexp = /this\.([\w]+)/g;
 
- 		let match;
 
- 		while ((match = regexp.exec(text)) !== null) {
 
- 			const propertyName = match[1];
 
- 			const from = { line: lineHandle.lineNo(), ch: match.index + 5 };
 
- 			const to = { line: lineHandle.lineNo(), ch: match.index + match[0].length };
 
- 			
 
- 			const foundProperty = this.findClassProperty(propertyName);
 
- 			if(!foundProperty)
 
- 				continue;
 
- 			
 
- 			editor.markText(from, to, {
 
- 				className: 'cm-this-prop',
 
- 				title: `Ctrl+Click to go to ${foundProperty.type === 'method' ? 'method' : 'property'} '${propertyName}'`,
 
- 				attributes: {
 
- 					[ClassPage.Attributes.DataPropertyName]: propertyName,
 
- 					[ClassPage.Attributes.DataPropertyType]: foundProperty.type === 'method' ? 'Methods' : 'Properties',
 
- 					[ClassPage.Attributes.DataPropertyParent]: foundProperty.inherited ? foundProperty.nearestParent : '',
 
- 					[ClassPage.Attributes.OnClick]: 'window.page.onPropertyClick(this);'
 
- 				}
 
- 			});
 
- 		}
 
- 	}
 
- 	markProperties(lineHandle, staticsRange) {
 
- 		const editor = this.codeMirrorEditor;
 
- 		const text = lineHandle.text;
 
- 		const lineNo = lineHandle.lineNo();
 
- 		const regexp = /\t([\w]+):/g;
 
- 		const isStatic = lineNo >= staticsRange.from && lineNo <= staticsRange.to;
 
- 		let match;
 
- 		while ((match = regexp.exec(text)) !== null) {
 
- 			const propertyName = isStatic ? `${ClassPage.__static__}${match[1]}` : match[1];
 
- 			const from = { line: lineNo, ch: match.index + 1 };
 
- 			const to = { line: lineNo, ch: match.index + match[0].length - 1 };
 
- 			
 
- 			const foundProperty = this.findClassProperty(propertyName);
 
- 			if(!foundProperty)
 
- 				continue;
 
- 			const titlePropertyName = isStatic ? `${Class[ClassPage.ClassProperties.ShortName] || Class[ClassPage.ClassProperties.Name]}.${propertyName.replace(ClassPage.__static__, '')}` : propertyName;
 
- 			editor.markText(from, to, {
 
- 				className: 'cm-this-prop',
 
- 				title: `Ctrl+Click to go to ${foundProperty.type === 'method' ? 'method' : 'property'} '${titlePropertyName}'`,
 
- 				attributes: {
 
- 					[ClassPage.Attributes.DataPropertyName]: propertyName,
 
- 					[ClassPage.Attributes.DataPropertyType]: foundProperty.type === 'method' ? 'Methods' : 'Properties',
 
- 					[ClassPage.Attributes.DataPropertyParent]: foundProperty.inherited ? foundProperty.nearestParent : '',
 
- 					[ClassPage.Attributes.OnClick]: 'window.page.onPropertyClick(this);'
 
- 				}
 
- 			});
 
- 		}
 
- 	}
 
- 	getStaticsRange() {
 
- 		const range = { from: -1, to: -1 };
 
- 		let staticsParenthesisFlag = -1;
 
- 		let found = false;
 
- 		this.codeMirrorEditor.cmEachLine((lineHandle) => {
 
- 			if(found)
 
- 				return;
 
- 			const text = lineHandle.text;
 
- 			let ignoreFirstParenthesis = false;
 
- 			if(staticsParenthesisFlag === -1 && text.match(/statics:\s*\{/)) {
 
- 				staticsParenthesisFlag = 1;
 
- 				ignoreFirstParenthesis = true;
 
- 				range.from = lineHandle.lineNo();
 
- 			}
 
- 			if(staticsParenthesisFlag > 0) {
 
- 				for(let i = 0; i < text.length; i++) {
 
- 					if(text.charAt(i) === '{' && ignoreFirstParenthesis)
 
- 						ignoreFirstParenthesis = false;
 
- 					else if (text.charAt(i) === '{')
 
- 						staticsParenthesisFlag++;
 
- 					else if(text.charAt(i) === '}')
 
- 						staticsParenthesisFlag--;
 
- 				}
 
- 			}
 
- 			if(staticsParenthesisFlag === 0) {
 
- 				range.to = lineHandle.lineNo();
 
- 				found = true;
 
- 			}
 
- 		});
 
- 		return range;
 
- 	}
 
- 	shortNameExists(shortName) {
 
- 		return Object.keys(ClassList).map((key) => ClassList[key]).filter((item) => item[ClassPage.ClassProperties.ShortName] === shortName).length > 0;
 
- 	}
 
- 	findClassProperty(propertyName) {
 
- 		if(propertyName.startsWith(ClassPage.__static__)) {
 
- 			propertyName = propertyName.slice(10);
 
- 			const statics = Class[ClassPage.ClassProperties.Statics];
 
- 			const foundStatic = statics.filter((prop) => prop.key === propertyName)[0];
 
- 			return foundStatic;
 
- 		}
 
- 		const dynamicProperties = Class[ClassPage.ClassProperties.DynamicProperties];
 
- 		const properties = Class[ClassPage.ClassProperties.Properties];
 
- 		const foundDynamic = dynamicProperties.filter((prop) => prop.key === propertyName)[0];
 
- 		const foundProperty = properties.filter((prop) => prop.key === propertyName)[0];
 
- 		return foundDynamic || foundProperty;
 
- 	}
 
- 	onClassLinkClick(fragment) {
 
- 		const codeMirrorEditorElement = this.codeMirrorEditorElement;
 
- 		if(codeMirrorEditorElement.hasClass(ClassPage.StyleClasses.CtrlPressed))
 
- 			Url.goTo(`/class/${fragment.getAttribute(ClassPage.Attributes.DataClassName)}`, codeMirrorEditorElement.hasClass(ClassPage.StyleClasses.ShiftPressed));
 
- 	}
 
- 	onPropertyClick(fragment) {
 
- 		const codeMirrorEditorElement = this.codeMirrorEditorElement;
 
- 		if(codeMirrorEditorElement.hasClass(ClassPage.StyleClasses.CtrlPressed)) {
 
- 			const parentClassName = fragment.getAttribute(ClassPage.Attributes.DataPropertyParent);
 
- 			const propertyType = fragment.getAttribute(ClassPage.Attributes.DataPropertyType);
 
- 			const propertyName = fragment.getAttribute(ClassPage.Attributes.DataPropertyName);
 
- 			if(parentClassName.length > 0)
 
- 				Url.goTo(`/class/${parentClassName}#${propertyType}:${propertyName}`);
 
- 			else
 
- 				Url.setHash(`${propertyType}:${propertyName}`).updateLocation();
 
- 		}
 
- 	}
 
- 	onTabClick(e) {
 
- 		const element = CDElement.get(e.target);
 
- 		if(element.hasClass(ClassPage.StyleClasses.Selected))
 
- 			return;
 
- 		this.openTab(element.getAttribute(ClassPage.Attributes.DataTab));
 
- 	}
 
- 	openTab(tabName) {
 
- 		this.selectTab(tabName);
 
- 		this.activateContent(tabName);
 
- 		Url.setHash(tabName).updateLocation();
 
- 	}
 
- 	onClassClick(e) {
 
- 		let element = CDElement.get(e.target); 
 
- 		while(!element.hasClass(ClassPage.StyleClasses.ClassItem))
 
- 			element = element.getParent();
 
- 		Url.goTo(`/class/${element.getAttribute(ClassPage.Attributes.DataClassName)}`);
 
- 	}
 
- 	onModeButtonClick(e) {
 
- 		const button = CDElement.get(e.target);
 
- 		const mode = button.getAttribute(ClassPage.Attributes.DataDisplayMode);
 
- 		this.switchMode(mode);
 
- 		button.addClass(ClassPage.StyleClasses.Selected);
 
- 		(mode === ClassPage.Mode.Tabs ? this.listModeButton : this.tabsModeButton).removeClass(ClassPage.StyleClasses.Selected);
 
- 	}
 
- 	searchInEditor(isStatic, ...queries) {
 
- 		this.switchFullSource(true);
 
- 		const editor = this.codeMirrorEditor;
 
- 		const staticsRange = this.getStaticsRange();
 
- 		const staticsStart = staticsRange.from;
 
- 		const staticsEnd = staticsRange.to;
 
- 		for(const query of queries) {
 
- 			const cursor = editor.getSearchCursor(query, CodeMirror.Pos(editor.cmFirstLine(), 0), { caseFold: false, multiline: true });
 
- 			while(cursor.find()) {
 
- 				const from = cursor.from();
 
- 				const to = cursor.to();
 
- 				const lineIndex = from.line;
 
- 				if((isStatic && lineIndex >= staticsStart && lineIndex <= staticsEnd) || (!isStatic && (lineIndex < staticsStart || lineIndex > staticsEnd))) {
 
- 					this.openTab('Editor');
 
- 					editor.setSelection(from, to);
 
- 					editor.scrollIntoView({ from: from, to: to }, 100);
 
- 					return;
 
- 				}
 
- 			}
 
- 		}
 
- 	}
 
- 	searchPropertyInEditor(isMethod, isDynamic, propertyName) {
 
- 		const isStatic = propertyName.startsWith(ClassPage.__static__);
 
- 		propertyName = isStatic ? propertyName.slice(10) : propertyName;
 
- 		if(isMethod) {
 
- 			this.searchInEditor(isStatic, `${propertyName}: function`);
 
- 		} else {
 
- 			if(isDynamic)
 
- 				this.searchInEditor(false, `this.${propertyName} =`, `this.${propertyName}`);
 
- 			else
 
- 				this.searchInEditor(isStatic, `${propertyName}: `);
 
- 		}
 
- 	}
 
- 	scrollToProperty(hashTab, hashProp) {
 
- 		if(!hashProp)
 
- 			return;
 
- 		
 
- 		const item = this.contentElements[hashTab].getFirstChild(`.property-item[data-property-name="${hashProp}"]`);
 
- 		
 
- 		if(!item)
 
- 			return;
 
- 		const categoryList = item.getParent();
 
- 		const categoryHeader = categoryList.previousSibling();
 
- 		
 
- 		if(categoryList.hasClass(ClassPage.StyleClasses.Hidden))
 
- 			categoryList.removeClass(ClassPage.StyleClasses.Hidden);
 
- 		if(categoryHeader.hasClass(ClassPage.StyleClasses.Collapsed))
 
- 			categoryHeader.removeClass(ClassPage.StyleClasses.Collapsed);
 
- 		item.addClass(ClassPage.StyleClasses.Highlighted);
 
- 		item.addClass(ClassPage.StyleClasses.White);
 
- 		if(this.mode === ClassPage.Mode.Tabs)
 
- 			this.contentElements[hashTab].scrollTo(`.property-item[data-property-name="${hashProp}"]`);
 
- 		else
 
- 			item.scrollIntoView();
 
- 		setTimeout(() => {
 
- 			item.removeClass(ClassPage.StyleClasses.White);
 
- 		}, 1000);
 
- 		setTimeout(() => {
 
- 			item.removeClass(ClassPage.StyleClasses.Highlighted);
 
- 		}, 2000);
 
- 	}
 
- 	delayedAdjustCommentInputHeight(e) {
 
- 		setTimeout(() => { this.adjustCommentInputHeight(e) }, 0);
 
- 	}
 
- 	adjustCommentInputHeight(e) {
 
- 		const textArea = e.target;
 
- 		if(textArea.scrollHeight < 422 || e.key === DOM.Keys.Backspace) {
 
- 			textArea.style.height = 'auto';
 
- 			textArea.style.height = `${Math.min(422, textArea.scrollHeight + 2)}px`;
 
- 		}
 
- 		
 
- 		textArea.scrollTop = textArea.scrollHeight;
 
- 	}
 
- 	applyHash() {
 
- 		const hash = (Url.getHash() || '').split(':');
 
- 		const hashTab = hash[0];
 
- 		const hashProp = hash[1];
 
- 		const tabElements = this.tabElements;
 
- 		const contentElements = this.contentElements;
 
- 		const selectedTab = tabElements[hashTab] || tabElements[ClassPage.TabNames.Editor];
 
- 		const activeContent = contentElements[hashTab] || contentElements[ClassPage.TabNames.Editor];
 
- 		this.selectTab(selectedTab);
 
- 		this.activateContent(activeContent);
 
- 		this.scrollToProperty(hashTab, hashProp);
 
- 	}
 
- 	openSocket() {
 
- 		this.socket = new Socket('/ws').onMessage(this.onSocketMessage.bind(this));
 
- 	}
 
- 	onSocketMessage(e) {
 
- 		const changes = JSON.parse(e.data) || [];
 
- 		changes.forEach((changedComment) => {
 
- 			this.processChange(changedComment);
 
- 		});
 
- 		if(this.statistics)
 
- 			this.statistics.load();
 
- 	}
 
- 	processChange(changedComment) {
 
- 		if(changedComment.root !== Class[ClassPage.ClassProperties.Root] || changedComment.className !== Class[ClassPage.ClassProperties.Name])
 
- 			return;
 
- 		const propertyItem = this.propertyItemElements[changedComment.propertyName];
 
- 		propertyItem.removeClass('saving');
 
- 		switch(changedComment.action) {
 
- 		case 'create':
 
- 			this.documented++;
 
- 			this.renderDocumentedPercentage();
 
- 			Comments[changedComment.propertyName] = changedComment;
 
- 			propertyItem.getFirstChild('.property-item-comment').append(this.createCommentDateElement(changedComment.timestamp, changedComment.author));
 
- 			break;
 
- 		case 'update':
 
- 			if(Comments[changedComment.propertyName].text.length === 0) {
 
- 				this.documented++;
 
- 				this.renderDocumentedPercentage();
 
- 			}
 
- 			
 
- 			Comments[changedComment.propertyName].text = changedComment.text;
 
- 			const dateElement = propertyItem.getFirstChild('.property-item-comment-date > .property-item-comment-date-date');
 
- 			if(dateElement) {
 
- 				dateElement.setInnerHTML(CDUtils.dateFormatUTC(changedComment.timestamp, 3, 'D.M.Y, H:I:S'));
 
- 			} else {
 
- 				propertyItem.getFirstChild('.property-item-comment').append(this.createCommentDateElement(changedComment.timestamp, changedComment.author));
 
- 			}
 
- 			break;
 
- 		case 'remove':
 
- 			this.documented--;
 
- 			if(Comments[changedComment.propertyName])
 
- 				Comments[changedComment.propertyName].text = '';
 
- 			this.renderDocumentedPercentage();
 
- 			propertyItem.getFirstChild('.property-item-comment-date').remove();
 
- 			break;
 
- 		}
 
- 		const commentContent = changedComment.text;
 
- 		const itemCommentStatic = propertyItem.getFirstChild('.property-item-comment-static');
 
- 		const itemCommentInput = propertyItem.getFirstChild('.property-item-comment-input');
 
- 		const itemCommentOkButton = propertyItem.getFirstChild('.property-item-comment-button');
 
- 		if(itemCommentInput) {
 
- 			itemCommentInput.setValue(commentContent);
 
- 			itemCommentInput.addClass(ClassPage.StyleClasses.Hidden);
 
- 			itemCommentOkButton.addClass(ClassPage.StyleClasses.Hidden);
 
- 		}
 
- 		
 
- 		itemCommentStatic.setInnerHTML(commentContent.length > 0 ? CDUtils.nl2br(commentContent) : 'Not commented yet...');
 
- 		itemCommentStatic.switchClass(ClassPage.StyleClasses.Empty, commentContent.length === 0);
 
- 		itemCommentStatic.removeClass(ClassPage.StyleClasses.Hidden);
 
- 	}
 
- 	renderContribution() {
 
- 		const contributionElement = this.contentElements[ClassPage.TabNames.Contribution];
 
- 		this.statistics = Statistics.init(contributionElement, Statistics.Modes.CLASS);
 
- 	}
 
- 	/*	>>> Context menu | TODO: move to a completely independent module? */
 
- 	showContextMenu(contextMenuType, target, pos) {
 
- 		while(!target.hasClass(contextMenuType))
 
- 			target = target.getParent();
 
- 		switch(contextMenuType) {
 
- 		case ClassPage.ContextMenuType.PropertyItem:
 
- 			const propertyItemName = target.getAttribute(ClassPage.Attributes.DataPropertyName);
 
- 			const propertyItemType = target.getAttribute(ClassPage.Attributes.DataPropertyType);
 
- 			const propertyItemParent = target.getAttribute(ClassPage.Attributes.DataPropertyParent);
 
- 			const propertyItemDynamic = target.getAttribute(ClassPage.Attributes.DataPropertyDynamic);
 
- 			const propertyItemInhertied = target.getAttribute(ClassPage.Attributes.DataPropertyInherited);
 
- 			if(propertyItemInhertied !== 'true') {
 
- 				this.createContextMenuItem('ShowInEditor', 'Show in Editor', () => {
 
- 					this.searchPropertyInEditor(propertyItemType === 'method', propertyItemDynamic === 'true', propertyItemName);
 
- 				});
 
- 			}
 
- 			if(propertyItemParent != null) {
 
- 				this.createContextMenuItem('MoveToParent', 'Move to parent', () => {
 
- 					Url.goTo(`/class/${propertyItemParent}#${propertyItemType === 'method' ? 'Methods' : 'Properties'}:${propertyItemName}`);
 
- 				});
 
- 			}
 
- 			if(isEditor && !target.getFirstChild('.property-item-comment-static').hasClass(ClassPage.StyleClasses.Hidden)) {
 
- 				this.createContextMenuDelimiter();
 
- 				this.createContextMenuItem('EditComment', 'Edit comment', () => {
 
- 					target.getFirstChild('.property-item-comment-static').click();
 
- 					target.getFirstChild('.property-item-comment-input').focus();
 
- 				});
 
- 			}
 
- 			this.createContextMenuDelimiter();
 
- 			this.createContextMenuItem('CopyLink', 'Copy link', () => {
 
- 				DOM.copyToClipboard(`${Url.getFullPath()}#${propertyItemType === 'method' ? 'Methods' : 'Properties'}:${propertyItemName}`);
 
- 			});
 
- 			break;
 
- 		}
 
- 		this.contextMenu.style('left', `${pos.x}px`).style('top', `${pos.y}px`);
 
- 		this.contextMenu.removeClass(ClassPage.StyleClasses.Hidden);
 
- 	}
 
- 	createContextMenuItem(name, text, action) {
 
- 		const itemAction = (e) => {
 
- 			action(e);
 
- 			this.hideContextMenu();
 
- 		};
 
- 		const item = DOM.create({ tag: DOM.Tags.Div, cls: 'context-menu-item', innerHTML: text, attr: { 'data-context-menu-item-name': name } }, this.contextMenu)
 
- 			.on(DOM.Events.Click, itemAction);
 
- 		this.contextMenuItems[name] = { item: item, action: itemAction };
 
- 	}
 
- 	createContextMenuDelimiter() {
 
- 		DOM.create({ tag: DOM.Tags.Div, cls: 'context-menu-delimiter' }, this.contextMenu);
 
- 	}
 
- 	clearContextMenu() {
 
- 		for(const item of this.contextMenu.getChildren()) {
 
- 			const name = item.getAttribute('data-context-menu-item-name');
 
- 			if(name) {
 
- 				const action = this.contextMenuItems[name].action;
 
- 				item.un(DOM.Events.Click, action);
 
- 				this.contextMenuItems[name] = null;
 
- 				delete this.contextMenuItems[name];
 
- 			}
 
- 			item.remove();
 
- 		}
 
- 	}
 
- 	hideContextMenu() {
 
- 		this.contextMenu.addClass(ClassPage.StyleClasses.Hidden);
 
- 		this.clearContextMenu();
 
- 	}
 
- 	
 
- 	/* <<< Context menu */
 
- };
 
- window_.on(DOM.Events.Load, (e) => {
 
- 	window.page = new ClassPage().start();
 
- });
 
- window_.on(DOM.Events.KeyDown, (e) => {
 
- 	if(window.page && e.key === DOM.Keys.Control)
 
- 		window.page.codeMirrorEditorElement.addClass(ClassPage.StyleClasses.CtrlPressed);
 
- 	if(window.page && e.key === DOM.Keys.Shift)
 
- 		window.page.codeMirrorEditorElement.addClass(ClassPage.StyleClasses.ShiftPressed);
 
- });
 
- window_.on(DOM.Events.KeyUp, (e) => {
 
- 	if(window.page && e.key === DOM.Keys.Control)
 
- 		window.page.codeMirrorEditorElement.removeClass(ClassPage.StyleClasses.CtrlPressed);
 
- 	if(window.page && e.key === DOM.Keys.Shift)
 
- 		window.page.codeMirrorEditorElement.removeClass(ClassPage.StyleClasses.ShiftPressed);
 
- });
 
- window_.on(DOM.Events.HashChange, (e) => {
 
- 	if(window.page)
 
- 		window.page.applyHash();
 
- });
 
- window_.on(DOM.Events.MouseDown, (e) => {
 
- 	if(window.page) {
 
- 		let target = CDElement.get(e.target);
 
- 		while(target != null && !target.hasClass('context-menu')) {
 
- 			target = target.getParent();
 
- 		}
 
- 		if(target != null && target.hasClass('context-menu'))
 
- 			return;
 
- 		if(!window.page.contextMenu.hasClass(ClassPage.StyleClasses.Hidden))
 
- 			window.page.hideContextMenu();
 
- 	}
 
- });
 
 
  |