|
@@ -98,7 +98,8 @@ class ClassPage {
|
|
MixedIn: 'mixedIn',
|
|
MixedIn: 'mixedIn',
|
|
ParentsBranch: 'parentsBranch',
|
|
ParentsBranch: 'parentsBranch',
|
|
Statics: 'statics',
|
|
Statics: 'statics',
|
|
- DynamicProperties: 'dynamicProperties'
|
|
|
|
|
|
+ DynamicProperties: 'dynamicProperties',
|
|
|
|
+ Root: 'root'
|
|
};
|
|
};
|
|
|
|
|
|
static ContextMenuType = {
|
|
static ContextMenuType = {
|
|
@@ -134,6 +135,7 @@ class ClassPage {
|
|
this.documentable = 0;
|
|
this.documentable = 0;
|
|
this.inheritedCommentsQuery = {};
|
|
this.inheritedCommentsQuery = {};
|
|
this.inheritedCommentsFields = {};
|
|
this.inheritedCommentsFields = {};
|
|
|
|
+ this.propertyItemElements = {};
|
|
|
|
|
|
this.documentedPercentage = DOM.get('.class-documented-percentage');
|
|
this.documentedPercentage = DOM.get('.class-documented-percentage');
|
|
|
|
|
|
@@ -165,6 +167,8 @@ class ClassPage {
|
|
|
|
|
|
this.applyHash();
|
|
this.applyHash();
|
|
|
|
|
|
|
|
+ this.openSocket();
|
|
|
|
+
|
|
return this;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -305,6 +309,7 @@ class ClassPage {
|
|
return element.hasClass('property-item-nearest-parent-span')
|
|
return element.hasClass('property-item-nearest-parent-span')
|
|
|| element.hasClass('property-item-comment-input')
|
|
|| element.hasClass('property-item-comment-input')
|
|
|| element.hasClass('property-item-comment-button')
|
|
|| element.hasClass('property-item-comment-button')
|
|
|
|
+ || element.hasClass('property-item-saving-filler')
|
|
|| (el.hasClass('property-item-comment-static') && el.hasClass(ClassPage.StyleClasses.Clickable));
|
|
|| (el.hasClass('property-item-comment-static') && el.hasClass(ClassPage.StyleClasses.Clickable));
|
|
};
|
|
};
|
|
|
|
|
|
@@ -314,9 +319,9 @@ class ClassPage {
|
|
const inheritedCommentsQuery = this.inheritedCommentsQuery;
|
|
const inheritedCommentsQuery = this.inheritedCommentsQuery;
|
|
properties.forEach((prop) => {
|
|
properties.forEach((prop) => {
|
|
if(inheritedCommentsQuery[prop.nearestParent]) {
|
|
if(inheritedCommentsQuery[prop.nearestParent]) {
|
|
- inheritedCommentsQuery[prop.nearestParent].push(prop.key);
|
|
|
|
|
|
+ inheritedCommentsQuery[prop.nearestParent].properties.push(prop.key);
|
|
} else {
|
|
} else {
|
|
- inheritedCommentsQuery[prop.nearestParent] = [prop.key];
|
|
|
|
|
|
+ inheritedCommentsQuery[prop.nearestParent] = { root: prop.nearestParentRoot, className: prop.nearestParent, properties: [prop.key] };
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
@@ -333,15 +338,17 @@ class ClassPage {
|
|
|
|
|
|
for(const property of properties) {
|
|
for(const property of properties) {
|
|
const propertyItem = DOM.create({ tag: DOM.Tags.Div, cls: 'property-item', attr: { [ClassPage.Attributes.DataPropertyName]: property.key, [ClassPage.Attributes.DataPropertyType]: property.type }}, propertiesList).on(DOM.Events.MouseDown, (e) => {
|
|
const propertyItem = DOM.create({ tag: DOM.Tags.Div, cls: 'property-item', attr: { [ClassPage.Attributes.DataPropertyName]: property.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(e.buttons === DOM.MouseButtons.Right) {
|
|
|
|
+ if(element.hasClass('property-item-saving-filler'))
|
|
|
|
+ return;
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
- this.showContextMenu(ClassPage.ContextMenuType.PropertyItem, CDElement.get(e.target), { x: e.pageX, y: e.pageY });
|
|
|
|
|
|
+ this.showContextMenu(ClassPage.ContextMenuType.PropertyItem, element, { x: e.pageX, y: e.pageY });
|
|
}, 10);
|
|
}, 10);
|
|
- } else if (e.buttons === DOM.MouseButtons.Left) {
|
|
|
|
- const element = CDElement.get(e.target);
|
|
|
|
|
|
+ } else if (e.buttons === DOM.MouseButtons.Left) {
|
|
if(propertyItemClickable(element))
|
|
if(propertyItemClickable(element))
|
|
return;
|
|
return;
|
|
-
|
|
|
|
if(type === ClassPage.PropertyType.Inherited) {
|
|
if(type === ClassPage.PropertyType.Inherited) {
|
|
Url.goTo(`/class/${property.nearestParent}#${isMethods ? 'Methods' : 'Properties'}:${property.key}`);
|
|
Url.goTo(`/class/${property.nearestParent}#${isMethods ? 'Methods' : 'Properties'}:${property.key}`);
|
|
} else {
|
|
} else {
|
|
@@ -382,8 +389,8 @@ class ClassPage {
|
|
const itemCommentText = DOM.create({ tag: DOM.Tags.Div, innerHTML: 'Comment:' });
|
|
const itemCommentText = DOM.create({ tag: DOM.Tags.Div, innerHTML: 'Comment:' });
|
|
const itemCommentCn = [itemCommentText];
|
|
const itemCommentCn = [itemCommentText];
|
|
|
|
|
|
- const loadedComment = Comments[type === ClassPage.PropertyType.Statics ? `static:${property.key}` : property.key];
|
|
|
|
- const loadedCommentText = loadedComment && typeof loadedComment === 'object' ? loadedComment.comment : '';
|
|
|
|
|
|
+ const loadedComment = Comments[type === ClassPage.PropertyType.Statics ? `__static__${property.key}` : property.key];
|
|
|
|
+ const loadedCommentText = loadedComment && typeof loadedComment === 'object' ? loadedComment.text : '';
|
|
|
|
|
|
const hasComment = loadedComment && typeof loadedComment === 'object' && loadedCommentText.length > 0;
|
|
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...' });
|
|
const itemCommentStatic = DOM.create({ tag: DOM.Tags.Div, cls: `property-item-comment-static${!hasComment ? ' empty' : ''}`, innerHTML: hasComment ? CDUtils.nl2br(loadedCommentText) : 'Not commented yet...' });
|
|
@@ -393,8 +400,10 @@ class ClassPage {
|
|
this.inheritedCommentsFields[`${property.nearestParent}:${property.key}`] = itemCommentStatic;
|
|
this.inheritedCommentsFields[`${property.nearestParent}:${property.key}`] = itemCommentStatic;
|
|
propertyItem.setAttribute(ClassPage.Attributes.DataPropertyInherited, 'true');
|
|
propertyItem.setAttribute(ClassPage.Attributes.DataPropertyInherited, 'true');
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ this.propertyItemElements[type === ClassPage.PropertyType.Statics ? `__static__${property.key}` : property.key] = propertyItem;
|
|
|
|
|
|
- if(isAdmin) {
|
|
|
|
|
|
+ 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));
|
|
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);
|
|
itemCommentCn.push(itemCommentInput);
|
|
|
|
|
|
@@ -411,42 +420,32 @@ class ClassPage {
|
|
|
|
|
|
const onCommentSave = (e) => {
|
|
const onCommentSave = (e) => {
|
|
const commentContent = itemCommentInput.getValue();
|
|
const commentContent = itemCommentInput.getValue();
|
|
- const propertyName = `${type === ClassPage.PropertyType.Statics ? 'static:' : ''}${property.key}`;
|
|
|
|
|
|
+ const propertyName = `${type === ClassPage.PropertyType.Statics ? '__static__' : ''}${property.key}`;
|
|
const className = Class[ClassPage.ClassProperties.Name];
|
|
const className = Class[ClassPage.ClassProperties.Name];
|
|
|
|
+ const classRoot = Class[ClassPage.ClassProperties.Root];
|
|
|
|
+
|
|
|
|
+ propertyItem.addClass('saving');
|
|
|
|
+ itemCommentInput.blur();
|
|
|
|
+
|
|
fetch('/updateComment', {
|
|
fetch('/updateComment', {
|
|
method: 'POST',
|
|
method: 'POST',
|
|
headers: {
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
},
|
|
body: new URLSearchParams({
|
|
body: new URLSearchParams({
|
|
|
|
+ 'root': classRoot,
|
|
'class': className,
|
|
'class': className,
|
|
'property': propertyName,
|
|
'property': propertyName,
|
|
'comment': commentContent
|
|
'comment': commentContent
|
|
})
|
|
})
|
|
}).then((res) => {
|
|
}).then((res) => {
|
|
- if(res.status === 200 || res.status === 201) {
|
|
|
|
- if(res.status === 201) {
|
|
|
|
- this.documented++;
|
|
|
|
- this.renderDocumentedPercentage();
|
|
|
|
- propertyItem.append(this.createCommentDateElement(new Date()));
|
|
|
|
- } else {
|
|
|
|
- if(commentContent.length === 0) {
|
|
|
|
- this.documented--;
|
|
|
|
- this.renderDocumentedPercentage();
|
|
|
|
- propertyItem.getFirstChild('.property-item-comment-date').remove();
|
|
|
|
- } else {
|
|
|
|
- propertyItem.getFirstChild('.property-item-comment-date > .property-item-comment-date-date').setInnerHTML(CDUtils.dateFormatUTC(new Date(), 3, 'D.M.Y, H:I:S'));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- itemCommentStatic.setInnerHTML(commentContent.length > 0 ? CDUtils.nl2br(commentContent) : 'Not commented yet...');
|
|
|
|
- itemCommentStatic.switchClass(ClassPage.StyleClasses.Empty, commentContent.length === 0);
|
|
|
|
- } else {
|
|
|
|
- console.error('Comment update failed.');
|
|
|
|
|
|
+ if(res.status !== 202) {
|
|
|
|
+ propertyItem.removeClass('saving');
|
|
|
|
+ console.error(`Comment update failed (${res.status})`);
|
|
}
|
|
}
|
|
- itemCommentInput.switchClass(ClassPage.StyleClasses.Hidden);
|
|
|
|
- itemCommentOkButton.switchClass(ClassPage.StyleClasses.Hidden);
|
|
|
|
- itemCommentStatic.switchClass(ClassPage.StyleClasses.Hidden);
|
|
|
|
|
|
+ }).catch((e) => {
|
|
|
|
+ propertyItem.removeClass('saving');
|
|
|
|
+ console.error(`Comment update failed`);
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
@@ -472,6 +471,8 @@ class ClassPage {
|
|
itemCommentStatic.switchClass(ClassPage.StyleClasses.Hidden);
|
|
itemCommentStatic.switchClass(ClassPage.StyleClasses.Hidden);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ DOM.create({ tag: DOM.Tags.Div, cls: 'property-item-saving-filler' }, propertyItem);
|
|
}
|
|
}
|
|
|
|
|
|
if(hasComment)
|
|
if(hasComment)
|
|
@@ -510,7 +511,7 @@ class ClassPage {
|
|
for(const prop of Object.keys(props)) {
|
|
for(const prop of Object.keys(props)) {
|
|
const element = this.inheritedCommentsFields[`${cls}:${prop}`];
|
|
const element = this.inheritedCommentsFields[`${cls}:${prop}`];
|
|
if(element) {
|
|
if(element) {
|
|
- element.setInnerHTML(props[prop].comment);
|
|
|
|
|
|
+ element.setInnerHTML(props[prop].text);
|
|
element.removeClass(ClassPage.StyleClasses.Empty);
|
|
element.removeClass(ClassPage.StyleClasses.Empty);
|
|
element.getParent().append(this.createCommentDateElement(props[prop].timestamp));
|
|
element.getParent().append(this.createCommentDateElement(props[prop].timestamp));
|
|
}
|
|
}
|
|
@@ -947,6 +948,62 @@ class ClassPage {
|
|
this.scrollToProperty(hashTab, hashProp);
|
|
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);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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();
|
|
|
|
+ propertyItem.append(this.createCommentDateElement(changedComment.timestamp));
|
|
|
|
+ break;
|
|
|
|
+ case 'update':
|
|
|
|
+ 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.append(this.createCommentDateElement(changedComment.timestamp));
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case 'remove':
|
|
|
|
+ this.documented--;
|
|
|
|
+ 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');
|
|
|
|
+
|
|
|
|
+ itemCommentInput.setValue(commentContent);
|
|
|
|
+ itemCommentStatic.setInnerHTML(commentContent.length > 0 ? CDUtils.nl2br(commentContent) : 'Not commented yet...');
|
|
|
|
+ itemCommentStatic.switchClass(ClassPage.StyleClasses.Empty, commentContent.length === 0);
|
|
|
|
+
|
|
|
|
+ itemCommentInput.addClass(ClassPage.StyleClasses.Hidden);
|
|
|
|
+ itemCommentOkButton.addClass(ClassPage.StyleClasses.Hidden);
|
|
|
|
+ itemCommentStatic.removeClass(ClassPage.StyleClasses.Hidden);
|
|
|
|
+ }
|
|
|
|
+
|
|
/* >>> Context menu | TODO: move to a completely independent module? */
|
|
/* >>> Context menu | TODO: move to a completely independent module? */
|
|
showContextMenu(contextMenuType, target, pos) {
|
|
showContextMenu(contextMenuType, target, pos) {
|
|
while(!target.hasClass(contextMenuType))
|
|
while(!target.hasClass(contextMenuType))
|
|
@@ -970,7 +1027,7 @@ class ClassPage {
|
|
Url.goTo(`/class/${propertyItemParent}#${propertyItemType === 'method' ? 'Methods' : 'Properties'}:${propertyItemName}`);
|
|
Url.goTo(`/class/${propertyItemParent}#${propertyItemType === 'method' ? 'Methods' : 'Properties'}:${propertyItemName}`);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
- if(isAdmin && !target.getFirstChild('.property-item-comment-static').hasClass(ClassPage.StyleClasses.Hidden)) {
|
|
|
|
|
|
+ if(isEditor && !target.getFirstChild('.property-item-comment-static').hasClass(ClassPage.StyleClasses.Hidden)) {
|
|
this.createContextMenuDelimiter();
|
|
this.createContextMenuDelimiter();
|
|
this.createContextMenuItem('EditComment', 'Edit comment', () => {
|
|
this.createContextMenuItem('EditComment', 'Edit comment', () => {
|
|
target.getFirstChild('.property-item-comment-static').click();
|
|
target.getFirstChild('.property-item-comment-static').click();
|