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',
Info: 'Info',
Contribution: 'Contribution'
};
static PropertyType = {
Statics: 'statics',
Base: 'base',
Overridden: 'overridden',
Dynamic: 'dynamic',
Inherited: 'inherited',
ClassComment: 'ClassComment'
};
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 __static__ = '__static__';
static __self__ = '__self__';
start() {
if(!Class[ClassPage.ClassProperties.Root]) {
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'),
[ClassPage.TabNames.Info]: DOM.get('.tab.info')
};
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'),
[ClassPage.TabNames.Info]: DOM.get('.content-tab#info')
};
if(isEditor) {
this.tabElements[ClassPage.TabNames.Contribution] = DOM.get('.tab.contribution');
this.contentElements[ClassPage.TabNames.Contribution] = DOM.get('.content-tab#contribution');
}
this.documented = 0; //Object.keys(Comments).filter((key) => { return Comments[key].text.length > 0; }).length;
this.documentable = 1; // __self__ property is always included
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');
this.contextMenu = ContextMenu.init();
(mode === ClassPage.Mode.Tabs ? tabsModeButton : listModeButton).addClass(ClassPage.StyleClasses.Selected);
rightContainer.addClass(mode);
this.loadDocumentedInfo();
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);
if(!Class.isPackage)
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)) {
if(tabElements[tabName])
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() {
if(!Class.isPackage) {
this.renderEditor();
this.renderParents();
this.renderMixins();
this.renderChildren();
this.renderMixedIn();
this.renderProperties();
this.renderMethods();
this.loadInheritedComments();
if(isEditor)
this.renderContribution();
}
this.renderDocumentedPercentage();
this.renderInfo();
}
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 onMouseDown = (e) => {
const element = CDElement.get(e.target);
const key = isStatics ? `${ClassPage.__static__}${property.key}` : property.key;
if(e.buttons === DOM.MouseButtons.Right) {
if(element.hasClass('property-item-saving-filler'))
return;
setTimeout(() => {
this.showContextMenu(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 data = { type: property.type, nearestParent: property.nearestParent, static: isStatics, value: property.value };
this.createPropertyItem(property.key, data, type, propertiesList, isMethods, onMouseDown);
}
propertiesHeader.on(DOM.Events.Click, (e) => {
propertiesList.switchClass(ClassPage.StyleClasses.Hidden);
propertiesHeader.switchClass(ClassPage.StyleClasses.Collapsed);
});
}
updateComment(classRoot, className, propertyName, commentContent) {
return fetch('/updateComment', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'root': classRoot,
'class': className,
'property': propertyName,
'comment': commentContent
})
});
}
createCommentDateElement(date, author) {
const commentDateText = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-comment-date-text', innerHTML: `Commented by ${author} 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);
}
renderInfo() {
const infoContainer = this.contentElements[ClassPage.TabNames.Info];
const propertiesList = DOM.create({ tag: DOM.Tags.Div, cls: 'properties-list' }, infoContainer);
this.createPropertyItem(ClassPage.__self__, { value: '' }, ClassPage.PropertyType.ClassComment, propertiesList);
}
createPropertyItem(name, data, type, container, isMethod, onMouseDown) {
const key = data.static ? `${ClassPage.__static__}${name}` : name;
const propertyItem = DOM.create({ tag: DOM.Tags.Div, cls: 'property-item', attr: { [ClassPage.Attributes.DataPropertyName]: key, [ClassPage.Attributes.DataPropertyType]: data.type }}, container).on(DOM.Events.MouseDown, (e) => {
if(onMouseDown)
onMouseDown(e);
});
if(type !== ClassPage.PropertyType.ClassComment) {
const itemNameText = DOM.create({ tag: DOM.Tags.Span, innerHTML: isMethod ? 'Signature: ' : 'Name: ' });
const itemNameValue = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-name-span', innerHTML: isMethod ? data.value : name });
DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-name', cn: [itemNameText, itemNameValue] }, propertyItem);
if(type !== ClassPage.PropertyType.Dynamic && !isMethod) {
const itemTypeText = DOM.create({ tag: DOM.Tags.Span, innerHTML: 'Type: ' });
const itemTypeValue = DOM.create({ tag: DOM.Tags.Span, cls: 'property-item-type-span', innerHTML: data.type });
DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-type', cn: [itemTypeText, itemTypeValue] }, propertyItem);
}
if(type !== ClassPage.PropertyType.Dynamic && !isMethod && data.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: data.type === 'string' ? `'${data.value}'` : data.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: data.nearestParent }).on('click', (e) => {
Url.goTo(`/class/${data.nearestParent}`);
});
DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-nearest-parent', cn: [itemParentText, itemParentValue] }, propertyItem);
propertyItem.setAttribute(ClassPage.Attributes.DataPropertyParent, data.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__}${name}` : name];
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[`${data.nearestParent}:${name}`] = itemCommentStatic;
propertyItem.setAttribute(ClassPage.Attributes.DataPropertyInherited, 'true');
}
this.propertyItemElements[type === ClassPage.PropertyType.Statics ? `${ClassPage.__static__}${name}` : name] = 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__ : ''}${name}`;
const className = Class[ClassPage.ClassProperties.Name];
const classRoot = Class[ClassPage.ClassProperties.Root];
propertyItem.addClass('saving');
itemCommentInput.blur();
this.updateComment(classRoot, className, propertyName, 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);
}
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() {
if(Class.isPackage)
return;
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.overridden ? 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] || (Class.isPackage ? tabElements[ClassPage.TabNames.Info] : tabElements[ClassPage.TabNames.Editor]);
const activeContent = contentElements[hashTab] || (Class.isPackage ? contentElements[ClassPage.TabNames.Info] : 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 */
showContextMenu(target, pos) {
while(!target.hasClass('property-item'))
target = target.getParent();
const contextMenu = this.contextMenu;
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);
const propertyVisualName = target.getFirstChild('.property-item-name-span').getValue();
if(propertyItemInhertied !== 'true') {
contextMenu.addItem('ShowInEditor', 'Show in Editor', () => {
this.searchPropertyInEditor(propertyItemType === 'method', propertyItemDynamic === 'true', propertyItemName);
});
}
if(propertyItemParent != null) {
contextMenu.addItem('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)) {
contextMenu.addDelimiter();
contextMenu.addItem('EditComment', 'Edit comment', () => {
target.getFirstChild('.property-item-comment-static').click();
target.getFirstChild('.property-item-comment-input').focus();
});
}
contextMenu.addDelimiter();
contextMenu.addItem('CopyLink', 'Copy link', () => {
DOM.copyToClipboard(`${Url.getFullPath()}#${propertyItemType === 'method' ? 'Methods' : 'Properties'}:${propertyItemName}`);
});
contextMenu.addDelimiter();
contextMenu.addItem('CopyHtmlLink', 'Copy HTML link', () => {
let linkText = propertyVisualName;
if(propertyItemName.startsWith(ClassPage.__static__))
linkText = `${Class[ClassPage.ClassProperties.ShortName] || Class[ClassPage.ClassProperties.Name]}.${propertyVisualName}`;
DOM.copyToClipboard(`${linkText}`);
});
contextMenu.show(pos);
}
/* <<< Context menu */
loadDocumentedInfo() {
let requestBody;
if(Class.isPackage) {
requestBody = new URLSearchParams({
'root': Class[ClassPage.ClassProperties.Root],
'packageName': Class[ClassPage.ClassProperties.Name]
});
} else {
requestBody = new URLSearchParams({
'className': Class[ClassPage.ClassProperties.Name]
});
}
fetch(`/docinfo`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: requestBody
}).then(res => res.json()).then((res) => {
this.documented = res['documented'];
this.documentable = res['documentable'];
this.renderDocumentedPercentage();
});
}
};
window_.on(DOM.Events.Load, (e) => {
window.page = new ClassPage().start();
});
window_.on(DOM.Events.KeyDown, (e) => {
if(Class.isPackage)
return;
if(window.page && (e.key === DOM.Keys.Control || e.key === DOM.Keys.Meta))
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(Class.isPackage)
return;
if(window.page && (e.key === DOM.Keys.Control || e.key === DOM.Keys.Meta))
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();
});