client/general: remove api calls from controls
Introduce some missing models along the way
This commit is contained in:
		
							parent
							
								
									54e3099c56
								
							
						
					
					
						commit
						a697aba1b0
					
				| @ -1,6 +1,4 @@ | ||||
| <div class='comments'> | ||||
|     <% if (ctx.canListComments && ctx.comments.length) { %> | ||||
|         <ul> | ||||
|         </ul> | ||||
|     <% } %> | ||||
|     <ul> | ||||
|     </ul> | ||||
| </div> | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const misc = require('../util/misc.js'); | ||||
| const PostList = require('../models/post_list.js'); | ||||
| const topNavigation = require('../models/top_navigation.js'); | ||||
| const PageController = require('../controllers/page_controller.js'); | ||||
| const CommentsPageView = require('../views/comments_page_view.js'); | ||||
| @ -10,25 +11,62 @@ class CommentsController { | ||||
|     constructor(ctx) { | ||||
|         topNavigation.activate('comments'); | ||||
| 
 | ||||
|         const proxy = PageController.createHistoryCacheProxy( | ||||
|             ctx, page => { | ||||
|                 const url = | ||||
|                     '/posts/?query=sort:comment-date+comment-count-min:1' + | ||||
|                     `&page=${page}&pageSize=10&fields=` + | ||||
|                     'id,comments,commentCount,thumbnailUrl'; | ||||
|                 return api.get(url); | ||||
|             }); | ||||
| 
 | ||||
|         this._pageController = new PageController({ | ||||
|             searchQuery: ctx.searchQuery, | ||||
|             clientUrl: '/comments/' + misc.formatSearchQuery({page: '{page}'}), | ||||
|             requestPage: PageController.createHistoryCacheProxy( | ||||
|                 ctx, | ||||
|                 page => { | ||||
|                     return api.get( | ||||
|                         '/posts/?query=sort:comment-date+comment-count-min:1' + | ||||
|                         `&page=${page}&pageSize=10&fields=` + | ||||
|                         'id,comments,commentCount,thumbnailUrl'); | ||||
|                 }), | ||||
|             requestPage: page => { | ||||
|                 return proxy(page).then(response => { | ||||
|                     return Promise.resolve(Object.assign( | ||||
|                         {}, | ||||
|                         response, | ||||
|                         {results: PostList.fromResponse(response.results)})); | ||||
|                 }); | ||||
|             }, | ||||
|             pageRenderer: pageCtx => { | ||||
|                 Object.assign(pageCtx, { | ||||
|                     canViewPosts: api.hasPrivilege('posts:view'), | ||||
|                 }); | ||||
|                 return new CommentsPageView(pageCtx); | ||||
|                 const view = new CommentsPageView(pageCtx); | ||||
|                 view.addEventListener('change', e => this._evtChange(e)); | ||||
|                 view.addEventListener('score', e => this._evtScore(e)); | ||||
|                 view.addEventListener('delete', e => this._evtDelete(e)); | ||||
|                 return view; | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     _evtChange(e) { | ||||
|         // TODO: disable form
 | ||||
|         e.detail.comment.text = e.detail.text; | ||||
|         e.detail.comment.save() | ||||
|             .catch(errorMessage => { | ||||
|                 e.detail.target.showError(errorMessage); | ||||
|                 // TODO: enable form
 | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtScore(e) { | ||||
|         e.detail.comment.setScore(e.detail.score) | ||||
|             .catch(errorMessage => { | ||||
|                 window.alert(errorMessage); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtDelete(e) { | ||||
|         e.detail.comment.delete() | ||||
|             .catch(errorMessage => { | ||||
|                 window.alert(errorMessage); | ||||
|             }); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| module.exports = router => { | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const misc = require('../util/misc.js'); | ||||
| const settings = require('../models/settings.js'); | ||||
| const Comment = require('../models/comment.js'); | ||||
| const Post = require('../models/post.js'); | ||||
| const topNavigation = require('../models/top_navigation.js'); | ||||
| const PostView = require('../views/post_view.js'); | ||||
| @ -17,6 +19,7 @@ class PostController { | ||||
|                     this._decorateSearchQuery('')), | ||||
|         ]).then(responses => { | ||||
|             const [post, aroundResponse] = responses; | ||||
|             this._post = post; | ||||
|             this._view = new PostView({ | ||||
|                 post: post, | ||||
|                 editMode: editMode, | ||||
| @ -26,6 +29,28 @@ class PostController { | ||||
|                 canListComments: api.hasPrivilege('comments:list'), | ||||
|                 canCreateComments: api.hasPrivilege('comments:create'), | ||||
|             }); | ||||
|             if (this._view.sidebarControl) { | ||||
|                 this._view.sidebarControl.addEventListener( | ||||
|                     'favorite', e => this._evtFavoritePost(e)); | ||||
|                 this._view.sidebarControl.addEventListener( | ||||
|                     'unfavorite', e => this._evtUnfavoritePost(e)); | ||||
|                 this._view.sidebarControl.addEventListener( | ||||
|                     'score', e => this._evtScorePost(e)); | ||||
|             } | ||||
|             if (this._view.commentFormControl) { | ||||
|                 this._view.commentFormControl.addEventListener( | ||||
|                     'change', e => this._evtCommentChange(e)); | ||||
|                 this._view.commentFormControl.addEventListener( | ||||
|                     'submit', e => this._evtCreateComment(e)); | ||||
|             } | ||||
|             if (this._view.commentListControl) { | ||||
|                 this._view.commentListControl.addEventListener( | ||||
|                     'change', e => this._evtUpdateComment(e)); | ||||
|                 this._view.commentListControl.addEventListener( | ||||
|                     'score', e => this._evtScoreComment(e)); | ||||
|                 this._view.commentListControl.addEventListener( | ||||
|                     'delete', e => this._evtDeleteComment(e)); | ||||
|             } | ||||
|         }, response => { | ||||
|             this._view = new EmptyView(); | ||||
|             this._view.showError(response.description); | ||||
| @ -45,6 +70,71 @@ class PostController { | ||||
|         } | ||||
|         return text.trim(); | ||||
|     } | ||||
| 
 | ||||
|     _evtCommentChange(e) { | ||||
|         misc.enableExitConfirmation(); | ||||
|     } | ||||
| 
 | ||||
|     _evtCreateComment(e) { | ||||
|         // TODO: disable form
 | ||||
|         const comment = Comment.create(this._post.id); | ||||
|         comment.text = e.detail.text; | ||||
|         comment.save() | ||||
|             .then(() => { | ||||
|                 this._post.comments.add(comment); | ||||
|                 this._view.commentFormControl.setText(''); | ||||
|                 // TODO: enable form
 | ||||
|                 misc.disableExitConfirmation(); | ||||
|             }, errorMessage => { | ||||
|                 this._view.commentFormControl.showError(errorMessage); | ||||
|                 // TODO: enable form
 | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtUpdateComment(e) { | ||||
|         // TODO: disable form
 | ||||
|         e.detail.comment.text = e.detail.text; | ||||
|         e.detail.comment.save() | ||||
|             .catch(errorMessage => { | ||||
|                 e.detail.target.showError(errorMessage); | ||||
|                 // TODO: enable form
 | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtScoreComment(e) { | ||||
|         e.detail.comment.setScore(e.detail.score) | ||||
|             .catch(errorMessage => { | ||||
|                 window.alert(errorMessage); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtDeleteComment(e) { | ||||
|         e.detail.comment.delete() | ||||
|             .catch(errorMessage => { | ||||
|                 window.alert(errorMessage); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtScorePost(e) { | ||||
|         e.detail.post.setScore(e.detail.score) | ||||
|             .catch(errorMessage => { | ||||
|                 window.alert(errorMessage); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtFavoritePost(e) { | ||||
|         e.detail.post.addToFavorites() | ||||
|             .catch(errorMessage => { | ||||
|                 window.alert(errorMessage); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtUnfavoritePost(e) { | ||||
|         e.detail.post.removeFromFavorites() | ||||
|             .catch(errorMessage => { | ||||
|                 window.alert(errorMessage); | ||||
|             }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = router => { | ||||
|  | ||||
| @ -1,98 +1,86 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const events = require('../events.js'); | ||||
| const views = require('../util/views.js'); | ||||
| const CommentFormControl = require('../controls/comment_form_control.js'); | ||||
| 
 | ||||
| class CommentControl { | ||||
|     constructor(hostNode, comment, settings) { | ||||
| const template = views.getTemplate('comment'); | ||||
| const scoreTemplate = views.getTemplate('score'); | ||||
| 
 | ||||
| class CommentControl extends events.EventTarget { | ||||
|     constructor(hostNode, comment) { | ||||
|         super(); | ||||
|         this._hostNode = hostNode; | ||||
|         this._comment = comment; | ||||
|         this._template = views.getTemplate('comment'); | ||||
|         this._scoreTemplate = views.getTemplate('score'); | ||||
|         this._settings = settings; | ||||
| 
 | ||||
|         this.install(); | ||||
|     } | ||||
|         comment.addEventListener('change', e => this._evtChange(e)); | ||||
|         comment.addEventListener('changeScore', e => this._evtChangeScore(e)); | ||||
| 
 | ||||
|     install() { | ||||
|         const isLoggedIn = api.isLoggedIn(this._comment.user); | ||||
|         const infix = isLoggedIn ? 'own' : 'any'; | ||||
|         const sourceNode = this._template({ | ||||
|         views.replaceContent(this._hostNode, template({ | ||||
|             comment: this._comment, | ||||
|             canViewUsers: api.hasPrivilege('users:view'), | ||||
|             canEditComment: api.hasPrivilege(`comments:edit:${infix}`), | ||||
|             canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`), | ||||
|         }); | ||||
|         })); | ||||
| 
 | ||||
|         if (this._editButtonNode) { | ||||
|             this._editButtonNode.addEventListener( | ||||
|                 'click', e => this._evtEditClick(e)); | ||||
|         } | ||||
|         if (this._deleteButtonNode) { | ||||
|             this._deleteButtonNode.addEventListener( | ||||
|                 'click', e => this._evtDeleteClick(e)); | ||||
|         } | ||||
| 
 | ||||
|         this._formControl = new CommentFormControl( | ||||
|             this._hostNode.querySelector('.comment-form-container'), | ||||
|             this._comment, | ||||
|             true); | ||||
|         events.proxyEvent(this._formControl, this, 'submit', 'change'); | ||||
| 
 | ||||
|         this._installScore(); | ||||
|     } | ||||
| 
 | ||||
|     get _scoreContainerNode() { | ||||
|         return this._hostNode.querySelector('.score-container'); | ||||
|     } | ||||
| 
 | ||||
|     get _editButtonNode() { | ||||
|         return this._hostNode.querySelector('.edit'); | ||||
|     } | ||||
| 
 | ||||
|     get _deleteButtonNode() { | ||||
|         return this._hostNode.querySelector('.delete'); | ||||
|     } | ||||
| 
 | ||||
|     get _upvoteButtonNode() { | ||||
|         return this._hostNode.querySelector('.upvote'); | ||||
|     } | ||||
| 
 | ||||
|     get _downvoteButtonNode() { | ||||
|         return this._hostNode.querySelector('.downvote'); | ||||
|     } | ||||
| 
 | ||||
|     _installScore() { | ||||
|         views.replaceContent( | ||||
|             sourceNode.querySelector('.score-container'), | ||||
|             this._scoreTemplate({ | ||||
|             this._scoreContainerNode, | ||||
|             scoreTemplate({ | ||||
|                 score: this._comment.score, | ||||
|                 ownScore: this._comment.ownScore, | ||||
|                 canScore: api.hasPrivilege('comments:score'), | ||||
|             })); | ||||
| 
 | ||||
|         const editButton = sourceNode.querySelector('.edit'); | ||||
|         const deleteButton = sourceNode.querySelector('.delete'); | ||||
|         const upvoteButton = sourceNode.querySelector('.upvote'); | ||||
|         const downvoteButton = sourceNode.querySelector('.downvote'); | ||||
| 
 | ||||
|         if (editButton) { | ||||
|             editButton.addEventListener( | ||||
|                 'click', e => this._evtEditClick(e)); | ||||
|         if (this._upvoteButtonNode) { | ||||
|             this._upvoteButtonNode.addEventListener( | ||||
|                 'click', e => this._evtScoreClick(e, 1)); | ||||
|         } | ||||
|         if (deleteButton) { | ||||
|             deleteButton.addEventListener( | ||||
|                 'click', e => this._evtDeleteClick(e)); | ||||
|         if (this._downvoteButtonNode) { | ||||
|             this._downvoteButtonNode.addEventListener( | ||||
|                 'click', e => this._evtScoreClick(e, -1)); | ||||
|         } | ||||
| 
 | ||||
|         if (upvoteButton) { | ||||
|             upvoteButton.addEventListener( | ||||
|                 'click', | ||||
|                 e => this._evtScoreClick( | ||||
|                     e, () => this._comment.ownScore === 1 ? 0 : 1)); | ||||
|         } | ||||
|         if (downvoteButton) { | ||||
|             downvoteButton.addEventListener( | ||||
|                 'click', | ||||
|                 e => this._evtScoreClick( | ||||
|                     e, () => this._comment.ownScore === -1 ? 0 : -1)); | ||||
|         } | ||||
| 
 | ||||
|         this._formControl = new CommentFormControl( | ||||
|             sourceNode.querySelector('.comment-form-container'), | ||||
|             this._comment, | ||||
|             { | ||||
|                 onSave: text => { | ||||
|                     return api.put('/comment/' + this._comment.id, { | ||||
|                         text: text, | ||||
|                     }).then(response => { | ||||
|                         this._comment = response; | ||||
|                         this.install(); | ||||
|                     }, response => { | ||||
|                         this._formControl.showError(response.description); | ||||
|                     }); | ||||
|                 }, | ||||
|                 canCancel: true | ||||
|             }); | ||||
| 
 | ||||
|         views.replaceContent(this._hostNode, sourceNode); | ||||
|     } | ||||
| 
 | ||||
|     _evtScoreClick(e, scoreGetter) { | ||||
|         e.preventDefault(); | ||||
|         api.put( | ||||
|             '/comment/' + this._comment.id + '/score', | ||||
|             {score: scoreGetter()}) | ||||
|         .then( | ||||
|             response => { | ||||
|                 this._comment.score = parseInt(response.score); | ||||
|                 this._comment.ownScore = parseInt(response.ownScore); | ||||
|                 this.install(); | ||||
|             }, response => { | ||||
|                 window.alert(response.description); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _evtEditClick(e) { | ||||
| @ -100,20 +88,34 @@ class CommentControl { | ||||
|         this._formControl.enterEditMode(); | ||||
|     } | ||||
| 
 | ||||
|     _evtScoreClick(e, score) { | ||||
|         e.preventDefault(); | ||||
|         this.dispatchEvent(new CustomEvent('score', { | ||||
|             detail: { | ||||
|                 comment: this._comment, | ||||
|                 score: this._comment.ownScore === score ? 0 : score, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     _evtDeleteClick(e) { | ||||
|         e.preventDefault(); | ||||
|         if (!window.confirm('Are you sure you want to delete this comment?')) { | ||||
|             return; | ||||
|         } | ||||
|         api.delete('/comment/' + this._comment.id) | ||||
|             .then(response => { | ||||
|                 if (this._settings.onDelete) { | ||||
|                     this._settings.onDelete(this._comment); | ||||
|                 } | ||||
|                 this._hostNode.parentNode.removeChild(this._hostNode); | ||||
|             }, response => { | ||||
|                 window.alert(response.description); | ||||
|             }); | ||||
|         this.dispatchEvent(new CustomEvent('delete', { | ||||
|             detail: { | ||||
|                 comment: this._comment, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     _evtChange(e) { | ||||
|         this._formControl.exitEditMode(); | ||||
|     } | ||||
| 
 | ||||
|     _evtChangeScore(e) { | ||||
|         this._installScore(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -1,19 +1,20 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const events = require('../events.js'); | ||||
| const misc = require('../util/misc.js'); | ||||
| const views = require('../util/views.js'); | ||||
| 
 | ||||
| class CommentFormControl { | ||||
|     constructor(hostNode, comment, settings) { | ||||
| const template = views.getTemplate('comment-form'); | ||||
| 
 | ||||
| class CommentFormControl extends events.EventTarget { | ||||
|     constructor(hostNode, comment, canCancel, minHeight) { | ||||
|         super(); | ||||
|         this._hostNode = hostNode; | ||||
|         this._comment = comment || {text: ''}; | ||||
|         this._template = views.getTemplate('comment-form'); | ||||
|         this._settings = settings; | ||||
|         this.install(); | ||||
|     } | ||||
|         this._canCancel = canCancel; | ||||
|         this._minHeight = minHeight || 150; | ||||
| 
 | ||||
|     install() { | ||||
|         const sourceNode = this._template({ | ||||
|         const sourceNode = template({ | ||||
|             comment: this._comment, | ||||
|         }); | ||||
| 
 | ||||
| @ -30,7 +31,7 @@ class CommentFormControl { | ||||
| 
 | ||||
|         formNode.addEventListener('submit', e => this._evtSaveClick(e)); | ||||
| 
 | ||||
|         if (this._settings.canCancel) { | ||||
|         if (this._canCancel) { | ||||
|             cancelButton | ||||
|                 .addEventListener('click', e => this._evtCancelClick(e)); | ||||
|         } else { | ||||
| @ -43,7 +44,11 @@ class CommentFormControl { | ||||
|             }); | ||||
|         } | ||||
|         textareaNode.addEventListener('change', e => { | ||||
|             misc.enableExitConfirmation(); | ||||
|             this.dispatchEvent(new CustomEvent('change', { | ||||
|                 detail: { | ||||
|                     target: this, | ||||
|                 }, | ||||
|             })); | ||||
|             this._growTextArea(); | ||||
|         }); | ||||
| 
 | ||||
| @ -60,7 +65,6 @@ class CommentFormControl { | ||||
|     exitEditMode() { | ||||
|         this._hostNode.classList.remove('editing'); | ||||
|         this._hostNode.querySelector('.tabs-wrapper').style.minHeight = null; | ||||
|         misc.disableExitConfirmation(); | ||||
|         views.clearMessages(this._hostNode); | ||||
|         this.setText(this._comment.text); | ||||
|     } | ||||
| @ -97,11 +101,13 @@ class CommentFormControl { | ||||
| 
 | ||||
|     _evtSaveClick(e) { | ||||
|         e.preventDefault(); | ||||
|         if (!this._settings.onSave) { | ||||
|             throw 'No save handler'; | ||||
|         } | ||||
|         this._settings.onSave(this._textareaNode.value) | ||||
|             .then(() => { misc.disableExitConfirmation(); }); | ||||
|         this.dispatchEvent(new CustomEvent('submit', { | ||||
|             detail: { | ||||
|                 target: this, | ||||
|                 comment: this._comment, | ||||
|                 text: this._textareaNode.value, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     _evtCancelClick(e) { | ||||
| @ -125,7 +131,7 @@ class CommentFormControl { | ||||
|     _growTextArea() { | ||||
|         this._textareaNode.style.height = | ||||
|             Math.max( | ||||
|                 this._settings.minHeight || 0, | ||||
|                 this._minHeight || 0, | ||||
|                 this._textareaNode.scrollHeight) + 'px'; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| @ -1,48 +1,58 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const events = require('../events.js'); | ||||
| const views = require('../util/views.js'); | ||||
| const CommentControl = require('../controls/comment_control.js'); | ||||
| 
 | ||||
| class CommentListControl { | ||||
|     constructor(hostNode, comments) { | ||||
| const template = views.getTemplate('comment-list'); | ||||
| 
 | ||||
| class CommentListControl extends events.EventTarget { | ||||
|     constructor(hostNode, comments, reversed) { | ||||
|         super(); | ||||
|         this._hostNode = hostNode; | ||||
|         this._comments = comments; | ||||
|         this._template = views.getTemplate('comment-list'); | ||||
|         this._commentIdToNode = {}; | ||||
| 
 | ||||
|         this.install(); | ||||
|         comments.addEventListener('add', e => this._evtAdd(e)); | ||||
|         comments.addEventListener('remove', e => this._evtRemove(e)); | ||||
| 
 | ||||
|         views.replaceContent(this._hostNode, template()); | ||||
| 
 | ||||
|         const commentList = Array.from(comments); | ||||
|         if (reversed) { | ||||
|             commentList.reverse(); | ||||
|         } | ||||
|         for (let comment of commentList) { | ||||
|             this._installCommentNode(comment); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     install() { | ||||
|         const sourceNode = this._template({ | ||||
|             comments: this._comments, | ||||
|             canListComments: api.hasPrivilege('comments:list'), | ||||
|         }); | ||||
| 
 | ||||
|         views.replaceContent(this._hostNode, sourceNode); | ||||
| 
 | ||||
|         this._renderComments(); | ||||
|     get _commentListNode() { | ||||
|         return this._hostNode.querySelector('ul'); | ||||
|     } | ||||
| 
 | ||||
|     _renderComments() { | ||||
|         if (!this._comments.length) { | ||||
|             return; | ||||
|         } | ||||
|         const commentList = new DocumentFragment(); | ||||
|         for (let comment of this._comments) { | ||||
|             const commentListItemNode = document.createElement('li'); | ||||
|             new CommentControl(commentListItemNode, comment, { | ||||
|                 onDelete: removedComment => { | ||||
|                     for (let [index, comment] of this._comments.entries()) { | ||||
|                         if (comment.id === removedComment.id) { | ||||
|                             this._comments.splice(index, 1); | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|             }); | ||||
|             commentList.appendChild(commentListItemNode); | ||||
|         } | ||||
|         views.replaceContent(this._hostNode.querySelector('ul'), commentList); | ||||
|     _installCommentNode(comment) { | ||||
|         const commentListItemNode = document.createElement('li'); | ||||
|         const commentControl = new CommentControl( | ||||
|             commentListItemNode, comment); | ||||
|         events.proxyEvent(commentControl, this, 'change'); | ||||
|         events.proxyEvent(commentControl, this, 'score'); | ||||
|         events.proxyEvent(commentControl, this, 'delete'); | ||||
|         this._commentIdToNode[comment.id] = commentListItemNode; | ||||
|         this._commentListNode.appendChild(commentListItemNode); | ||||
|     } | ||||
| 
 | ||||
|     _uninstallCommentNode(comment) { | ||||
|         const commentListItemNode = this._commentIdToNode[comment.id]; | ||||
|         commentListItemNode.parentNode.removeChild(commentListItemNode); | ||||
|     } | ||||
| 
 | ||||
|     _evtAdd(e) { | ||||
|         this._installCommentNode(e.detail.comment); | ||||
|     } | ||||
| 
 | ||||
|     _evtRemove(e) { | ||||
|         this._uninstallCommentNode(e.detail.comment); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -1,22 +1,20 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const events = require('../events.js'); | ||||
| const views = require('../util/views.js'); | ||||
| 
 | ||||
| class PostEditSidebarControl { | ||||
| const template = views.getTemplate('post-edit-sidebar'); | ||||
| 
 | ||||
| class PostEditSidebarControl extends events.EventTarget { | ||||
|     constructor(hostNode, post, postContentControl) { | ||||
|         super(); | ||||
|         this._hostNode = hostNode; | ||||
|         this._post = post; | ||||
|         this._postContentControl = postContentControl; | ||||
|         this._template = views.getTemplate('post-edit-sidebar'); | ||||
| 
 | ||||
|         this.install(); | ||||
|     } | ||||
| 
 | ||||
|     install() { | ||||
|         const sourceNode = this._template({ | ||||
|         views.replaceContent(this._hostNode, template({ | ||||
|             post: this._post, | ||||
|         }); | ||||
|         views.replaceContent(this._hostNode, sourceNode); | ||||
|         })); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -1,93 +1,128 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const events = require('../events.js'); | ||||
| const tags = require('../tags.js'); | ||||
| const views = require('../util/views.js'); | ||||
| 
 | ||||
| class PostReadonlySidebarControl { | ||||
| const template = views.getTemplate('post-readonly-sidebar'); | ||||
| const scoreTemplate = views.getTemplate('score'); | ||||
| const favTemplate = views.getTemplate('fav'); | ||||
| 
 | ||||
| class PostReadonlySidebarControl extends events.EventTarget { | ||||
|     constructor(hostNode, post, postContentControl) { | ||||
|         super(); | ||||
|         this._hostNode = hostNode; | ||||
|         this._post = post; | ||||
|         this._postContentControl = postContentControl; | ||||
|         this._template = views.getTemplate('post-readonly-sidebar'); | ||||
|         this._scoreTemplate = views.getTemplate('score'); | ||||
|         this._favTemplate = views.getTemplate('fav'); | ||||
| 
 | ||||
|         this.install(); | ||||
|     } | ||||
|         post.addEventListener('changeFavorite', e => this._evtChangeFav(e)); | ||||
|         post.addEventListener('changeScore', e => this._evtChangeScore(e)); | ||||
| 
 | ||||
|     install() { | ||||
|         const sourceNode = this._template({ | ||||
|         views.replaceContent(this._hostNode, template({ | ||||
|             post: this._post, | ||||
|             getTagCategory: this._getTagCategory, | ||||
|             getTagUsages: this._getTagUsages, | ||||
|             canListPosts: api.hasPrivilege('posts:list'), | ||||
|             canViewTags: api.hasPrivilege('tags:view'), | ||||
|         }); | ||||
|         })); | ||||
| 
 | ||||
|         views.replaceContent( | ||||
|             sourceNode.querySelector('.score-container'), | ||||
|             this._scoreTemplate({ | ||||
|                 score: this._post.score, | ||||
|                 ownScore: this._post.ownScore, | ||||
|                 canScore: api.hasPrivilege('posts:score'), | ||||
|             })); | ||||
|         this._installFav(); | ||||
|         this._installScore(); | ||||
|         this._installFitButtons(); | ||||
|         this._syncFitButton(); | ||||
|     } | ||||
| 
 | ||||
|     get _scoreContainerNode() { | ||||
|         return this._hostNode.querySelector('.score-container'); | ||||
|     } | ||||
| 
 | ||||
|     get _favContainerNode() { | ||||
|         return this._hostNode.querySelector('.fav-container'); | ||||
|     } | ||||
| 
 | ||||
|     get _upvoteButtonNode() { | ||||
|         return this._hostNode.querySelector('.upvote'); | ||||
|     } | ||||
| 
 | ||||
|     get _downvoteButtonNode() { | ||||
|         return this._hostNode.querySelector('.downvote'); | ||||
|     } | ||||
| 
 | ||||
|     get _addFavButtonNode() { | ||||
|         return this._hostNode.querySelector('.add-favorite'); | ||||
|     } | ||||
| 
 | ||||
|     get _remFavButtonNode() { | ||||
|         return this._hostNode.querySelector('.remove-favorite'); | ||||
|     } | ||||
| 
 | ||||
|     get _fitBothButtonNode() { | ||||
|         return this._hostNode.querySelector('.fit-both'); | ||||
|     } | ||||
| 
 | ||||
|     get _fitOriginalButtonNode() { | ||||
|         return this._hostNode.querySelector('.fit-original'); | ||||
|     } | ||||
| 
 | ||||
|     get _fitWidthButtonNode() { | ||||
|         return this._hostNode.querySelector('.fit-width'); | ||||
|     } | ||||
| 
 | ||||
|     get _fitHeightButtonNode() { | ||||
|         return this._hostNode.querySelector('.fit-height'); | ||||
|     } | ||||
| 
 | ||||
|     _installFitButtons() { | ||||
|         this._fitBothButtonNode.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitBoth())); | ||||
|         this._fitOriginalButtonNode.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitOriginal())); | ||||
|         this._fitWidthButtonNode.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitWidth())); | ||||
|         this._fitHeightButtonNode.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitHeight())); | ||||
|     } | ||||
| 
 | ||||
|     _installFav() { | ||||
|         views.replaceContent( | ||||
|             sourceNode.querySelector('.fav-container'), | ||||
|             this._favTemplate({ | ||||
|             this._favContainerNode, | ||||
|             favTemplate({ | ||||
|                 favoriteCount: this._post.favoriteCount, | ||||
|                 ownFavorite: this._post.ownFavorite, | ||||
|                 canFavorite: api.hasPrivilege('posts:favorite'), | ||||
|             })); | ||||
| 
 | ||||
|         const upvoteButton = sourceNode.querySelector('.upvote'); | ||||
|         const downvoteButton = sourceNode.querySelector('.downvote'); | ||||
|         const addFavButton = sourceNode.querySelector('.add-favorite'); | ||||
|         const remFavButton = sourceNode.querySelector('.remove-favorite'); | ||||
|         const fitBothButton = sourceNode.querySelector('.fit-both'); | ||||
|         const fitOriginalButton = sourceNode.querySelector('.fit-original'); | ||||
|         const fitWidthButton = sourceNode.querySelector('.fit-width'); | ||||
|         const fitHeightButton = sourceNode.querySelector('.fit-height'); | ||||
| 
 | ||||
|         if (upvoteButton) { | ||||
|             upvoteButton.addEventListener( | ||||
|                 'click', this._eventRequestProxy( | ||||
|                     () => this._setScore(this._post.ownScore === 1 ? 0 : 1))); | ||||
|         if (this._addFavButtonNode) { | ||||
|             this._addFavButtonNode.addEventListener( | ||||
|                 'click', e => this._evtAddToFavoritesClick(e)); | ||||
|         } | ||||
|         if (downvoteButton) { | ||||
|             downvoteButton.addEventListener( | ||||
|                 'click', this._eventRequestProxy( | ||||
|                     () => this._setScore(this._post.ownScore === -1 ? 0 : -1))); | ||||
|         if (this._remFavButtonNode) { | ||||
|             this._remFavButtonNode.addEventListener( | ||||
|                 'click', e => this._evtRemoveFromFavoritesClick(e)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|         if (addFavButton) { | ||||
|             addFavButton.addEventListener( | ||||
|                 'click', this._eventRequestProxy( | ||||
|                     () => this._addToFavorites())); | ||||
|     _installScore() { | ||||
|         views.replaceContent( | ||||
|             this._scoreContainerNode, | ||||
|             scoreTemplate({ | ||||
|                 score: this._post.score, | ||||
|                 ownScore: this._post.ownScore, | ||||
|                 canScore: api.hasPrivilege('posts:score'), | ||||
|             })); | ||||
|         if (this._upvoteButtonNode) { | ||||
|             this._upvoteButtonNode.addEventListener( | ||||
|                 'click', e => this._evtScoreClick(e, 1)); | ||||
|         } | ||||
|         if (remFavButton) { | ||||
|             remFavButton.addEventListener( | ||||
|                 'click', this._eventRequestProxy( | ||||
|                     () => this._removeFromFavorites())); | ||||
|         if (this._downvoteButtonNode) { | ||||
|             this._downvoteButtonNode.addEventListener( | ||||
|                 'click', e => this._evtScoreClick(e, -1)); | ||||
|         } | ||||
| 
 | ||||
|         fitBothButton.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitBoth())); | ||||
|         fitOriginalButton.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitOriginal())); | ||||
|         fitWidthButton.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitWidth())); | ||||
|         fitHeightButton.addEventListener( | ||||
|             'click', this._eventZoomProxy( | ||||
|                 () => this._postContentControl.fitHeight())); | ||||
| 
 | ||||
|         views.replaceContent(this._hostNode, sourceNode); | ||||
| 
 | ||||
|         this._syncFitButton(); | ||||
|     } | ||||
| 
 | ||||
|     _eventZoomProxy(func) { | ||||
| @ -99,15 +134,6 @@ class PostReadonlySidebarControl { | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     _eventRequestProxy(promise) { | ||||
|         return e => { | ||||
|             e.preventDefault(); | ||||
|             promise().then(() => { | ||||
|                 this.install(); | ||||
|             }); | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     _syncFitButton() { | ||||
|         const funcToClassName = {}; | ||||
|         funcToClassName[this._postContentControl.fitBoth] = 'fit-both'; | ||||
| @ -134,37 +160,40 @@ class PostReadonlySidebarControl { | ||||
|         return tag ? tag.category : 'unknown'; | ||||
|     } | ||||
| 
 | ||||
|     _setScore(score) { | ||||
|         return this._requestAndRefresh( | ||||
|             () => api.put('/post/' + this._post.id + '/score', {score: score})); | ||||
|     _evtAddToFavoritesClick(e) { | ||||
|         e.preventDefault(); | ||||
|         this.dispatchEvent(new CustomEvent('favorite', { | ||||
|             detail: { | ||||
|                 post: this._post, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     _addToFavorites() { | ||||
|         return this._requestAndRefresh( | ||||
|             () => api.post('/post/' + this._post.id + '/favorite')); | ||||
|     _evtRemoveFromFavoritesClick(e) { | ||||
|         e.preventDefault(); | ||||
|         this.dispatchEvent(new CustomEvent('unfavorite', { | ||||
|             detail: { | ||||
|                 post: this._post, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     _removeFromFavorites() { | ||||
|         return this._requestAndRefresh( | ||||
|             () => api.delete('/post/' + this._post.id + '/favorite')); | ||||
|     _evtScoreClick(e, score) { | ||||
|         e.preventDefault(); | ||||
|         this.dispatchEvent(new CustomEvent('score', { | ||||
|             detail: { | ||||
|                 post: this._post, | ||||
|                 score: this._post.ownScore === score ? 0 : score, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     _requestAndRefresh(requestPromise) { | ||||
|         return new Promise((resolve, reject) => { | ||||
|             requestPromise() | ||||
|                 .then( | ||||
|                     response => { return api.get('/post/' + this._post.id); }, | ||||
|                     response => { return Promise.reject(response); }) | ||||
|                 .then( | ||||
|                     response => { | ||||
|                         this._post = response; | ||||
|                         resolve(); | ||||
|                     }, | ||||
|                     response => { | ||||
|                         reject(); | ||||
|                         window.alert(response.description); | ||||
|                     }); | ||||
|         }); | ||||
|     _evtChangeFav(e) { | ||||
|         this._installFav(); | ||||
|     } | ||||
| 
 | ||||
|     _evtChangeScore(e) { | ||||
|         this._installScore(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -13,10 +13,22 @@ class EventTarget { | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| function proxyEvent(source, target, sourceEventType, targetEventType) { | ||||
|     if (!targetEventType) { | ||||
|         targetEventType = sourceEventType; | ||||
|     } | ||||
|     source.addEventListener(sourceEventType, e => { | ||||
|         target.dispatchEvent(new CustomEvent(targetEventType, { | ||||
|             detail: e.detail, | ||||
|         })); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
|     Success: 'success', | ||||
|     Error: 'error', | ||||
|     Info: 'info', | ||||
| 
 | ||||
|     proxyEvent: proxyEvent, | ||||
|     EventTarget: EventTarget, | ||||
| }; | ||||
|  | ||||
							
								
								
									
										118
									
								
								client/js/models/comment.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								client/js/models/comment.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const events = require('../events.js'); | ||||
| 
 | ||||
| class Comment extends events.EventTarget { | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this.commentList = null; | ||||
| 
 | ||||
|         this._id = null; | ||||
|         this._postId = null; | ||||
|         this._text = null; | ||||
|         this._user = null; | ||||
|         this._creationTime = null; | ||||
|         this._lastEditTime = null; | ||||
|         this._score = null; | ||||
|         this._ownScore = null; | ||||
|     } | ||||
| 
 | ||||
|     static create(postId) { | ||||
|         const comment = new Comment(); | ||||
|         comment._postId = postId; | ||||
|         return comment; | ||||
|     } | ||||
| 
 | ||||
|     static fromResponse(response) { | ||||
|         const comment = new Comment(); | ||||
|         comment._updateFromResponse(response); | ||||
|         return comment; | ||||
|     } | ||||
| 
 | ||||
|     get id() { return this._id; } | ||||
|     get postId() { return this._postId; } | ||||
|     get text() { return this._text; } | ||||
|     get user() { return this._user; } | ||||
|     get creationTime() { return this._creationTime; } | ||||
|     get lastEditTime() { return this._lastEditTime; } | ||||
|     get score() { return this._score; } | ||||
|     get ownScore() { return this._ownScore; } | ||||
| 
 | ||||
|     set text(value) { this._text = value; } | ||||
| 
 | ||||
|     save() { | ||||
|         let promise = null; | ||||
|         if (this._id) { | ||||
|             promise = api.put( | ||||
|                 '/comment/' + this._id, | ||||
|                 { | ||||
|                     text: this._text, | ||||
|                 }); | ||||
|         } else { | ||||
|             promise = api.post( | ||||
|                 '/comments', | ||||
|                 { | ||||
|                     text: this._text, | ||||
|                     postId: this._postId, | ||||
|                 }); | ||||
|         } | ||||
| 
 | ||||
|         return promise.then(response => { | ||||
|             this._updateFromResponse(response); | ||||
|             this.dispatchEvent(new CustomEvent('change', { | ||||
|                 details: { | ||||
|                     comment: this, | ||||
|                 }, | ||||
|             })); | ||||
|             return Promise.resolve(); | ||||
|         }, response => { | ||||
|             return Promise.reject(response.description); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     delete() { | ||||
|         return api.delete('/comment/' + this._id) | ||||
|             .then(response => { | ||||
|                 if (this.commentList) { | ||||
|                     this.commentList.remove(this); | ||||
|                 } | ||||
|                 this.dispatchEvent(new CustomEvent('delete', { | ||||
|                     details: { | ||||
|                         comment: this, | ||||
|                     }, | ||||
|                 })); | ||||
|                 return Promise.resolve(); | ||||
|             }, response => { | ||||
|                 return Promise.reject(response.description); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     setScore(score) { | ||||
|         return api.put('/comment/' + this._id + '/score', {score: score}) | ||||
|             .then(response => { | ||||
|                 this._updateFromResponse(response); | ||||
|                 this.dispatchEvent(new CustomEvent('changeScore', { | ||||
|                     details: { | ||||
|                         comment: this, | ||||
|                     }, | ||||
|                 })); | ||||
|                 return Promise.resolve(); | ||||
|             }, response => { | ||||
|                 return Promise.reject(response.description); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _updateFromResponse(response) { | ||||
|         this._id = response.id; | ||||
|         this._postId = response.postId; | ||||
|         this._text = response.text; | ||||
|         this._user = response.user; | ||||
|         this._creationTime = response.creationTime; | ||||
|         this._lastEditTime = response.lastEditTime; | ||||
|         this._score = parseInt(response.score); | ||||
|         this._ownScore = parseInt(response.ownScore); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = Comment; | ||||
							
								
								
									
										59
									
								
								client/js/models/comment_list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								client/js/models/comment_list.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const events = require('../events.js'); | ||||
| const Comment = require('./comment.js'); | ||||
| 
 | ||||
| class CommentList extends events.EventTarget { | ||||
|     constructor(comments) { | ||||
|         super(); | ||||
|         this._list = []; | ||||
|     } | ||||
| 
 | ||||
|     static fromResponse(commentsResponse) { | ||||
|         const commentList = new CommentList(); | ||||
|         for (let commentResponse of commentsResponse) { | ||||
|             const comment = Comment.fromResponse(commentResponse); | ||||
|             comment.commentList = commentList; | ||||
|             commentList._list.push(comment); | ||||
|         } | ||||
|         return commentList; | ||||
|     } | ||||
| 
 | ||||
|     get comments() { | ||||
|         return [...this._list]; | ||||
|     } | ||||
| 
 | ||||
|     add(comment) { | ||||
|         comment.commentList = this; | ||||
|         this._list.push(comment); | ||||
|         this.dispatchEvent(new CustomEvent('add', { | ||||
|             detail: { | ||||
|                 comment: comment, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     remove(commentToRemove) { | ||||
|         for (let [index, comment] of this._list.entries()) { | ||||
|             if (comment.id === commentToRemove.id) { | ||||
|                 this._list.splice(index, 1); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         this.dispatchEvent(new CustomEvent('remove', { | ||||
|             detail: { | ||||
|                 comment: commentToRemove, | ||||
|             }, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     get length() { | ||||
|         return this._list.length; | ||||
|     } | ||||
| 
 | ||||
|     [Symbol.iterator]() { | ||||
|         return this._list[Symbol.iterator](); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = CommentList; | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const events = require('../events.js'); | ||||
| const CommentList = require('./comment_list.js'); | ||||
| 
 | ||||
| class Post extends events.EventTarget { | ||||
|     constructor() { | ||||
| @ -29,7 +30,22 @@ class Post extends events.EventTarget { | ||||
|         this._ownFavorite = null; | ||||
|     } | ||||
| 
 | ||||
|     // encapsulation - don't let set these casually
 | ||||
|     static fromResponse(response) { | ||||
|         const post = new Post(); | ||||
|         post._updateFromResponse(response); | ||||
|         return post; | ||||
|     } | ||||
| 
 | ||||
|     static get(id) { | ||||
|         return api.get('/post/' + id) | ||||
|             .then(response => { | ||||
|                 const post = Post.fromResponse(response); | ||||
|                 return Promise.resolve(post); | ||||
|             }, response => { | ||||
|                 return Promise.reject(response); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     get id() { return this._id; } | ||||
|     get type() { return this._type; } | ||||
|     get mimeType() { return this._mimeType; } | ||||
| @ -52,37 +68,97 @@ class Post extends events.EventTarget { | ||||
|     get ownFavorite() { return this._ownFavorite; } | ||||
|     get ownScore() { return this._ownScore; } | ||||
| 
 | ||||
|     static get(id) { | ||||
|         return new Promise((resolve, reject) => { | ||||
|             api.get('/post/' + id) | ||||
|                 .then(response => { | ||||
|                     const post = new Post(); | ||||
|                     post._id = response.id; | ||||
|                     post._type = response.type; | ||||
|                     post._mimeType = response.mimeType; | ||||
|                     post._creationTime = response.creationTime; | ||||
|                     post._user = response.user; | ||||
|                     post._safety = response.safety; | ||||
|                     post._contentUrl = response.contentUrl; | ||||
|                     post._thumbnailUrl = response.thumbnailUrl; | ||||
|                     post._canvasWidth = response.canvasWidth; | ||||
|                     post._canvasHeight = response.canvasHeight; | ||||
|                     post._fileSize = response.fileSize; | ||||
|     setScore(score) { | ||||
|         return api.put('/post/' + this._id + '/score', {score: score}) | ||||
|             .then(response => { | ||||
|                 const prevFavorite = this._ownFavorite; | ||||
|                 this._updateFromResponse(response); | ||||
|                 if (this._ownFavorite !== prevFavorite) { | ||||
|                     this.dispatchEvent(new CustomEvent('changeFavorite', { | ||||
|                         details: { | ||||
|                             post: this, | ||||
|                         }, | ||||
|                     })); | ||||
|                 } | ||||
|                 this.dispatchEvent(new CustomEvent('changeScore', { | ||||
|                     details: { | ||||
|                         post: this, | ||||
|                     }, | ||||
|                 })); | ||||
|                 return Promise.resolve(); | ||||
|             }, response => { | ||||
|                 return Promise.reject(response.description); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|                     post._tags = response.tags; | ||||
|                     post._notes = response.notes; | ||||
|                     post._comments = response.comments; | ||||
|                     post._relations = response.relations; | ||||
|     addToFavorites() { | ||||
|         return api.post('/post/' + this.id + '/favorite') | ||||
|             .then(response => { | ||||
|                 const prevScore = this._ownScore; | ||||
|                 this._updateFromResponse(response); | ||||
|                 if (this._ownScore !== prevScore) { | ||||
|                     this.dispatchEvent(new CustomEvent('changeScore', { | ||||
|                         details: { | ||||
|                             post: this, | ||||
|                         }, | ||||
|                     })); | ||||
|                 } | ||||
|                 this.dispatchEvent(new CustomEvent('changeFavorite', { | ||||
|                     details: { | ||||
|                         post: this, | ||||
|                     }, | ||||
|                 })); | ||||
|                 return Promise.resolve(); | ||||
|             }, response => { | ||||
|                 return Promise.reject(response.description); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|                     post._score = response.score; | ||||
|                     post._favoriteCount = response.favoriteCount; | ||||
|                     post._ownScore = response.ownScore; | ||||
|                     post._ownFavorite = response.ownFavorite; | ||||
|                     resolve(post); | ||||
|                 }, response => { | ||||
|                     reject(response); | ||||
|                 }); | ||||
|         }); | ||||
|     removeFromFavorites() { | ||||
|         return api.delete('/post/' + this.id + '/favorite') | ||||
|             .then(response => { | ||||
|                 const prevScore = this._ownScore; | ||||
|                 this._updateFromResponse(response); | ||||
|                 if (this._ownScore !== prevScore) { | ||||
|                     this.dispatchEvent(new CustomEvent('changeScore', { | ||||
|                         details: { | ||||
|                             post: this, | ||||
|                         }, | ||||
|                     })); | ||||
|                 } | ||||
|                 this.dispatchEvent(new CustomEvent('changeFavorite', { | ||||
|                     details: { | ||||
|                         post: this, | ||||
|                     }, | ||||
|                 })); | ||||
|                 return Promise.resolve(); | ||||
|             }, response => { | ||||
|                 return Promise.reject(response.description); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     _updateFromResponse(response) { | ||||
|         this._id = response.id; | ||||
|         this._type = response.type; | ||||
|         this._mimeType = response.mimeType; | ||||
|         this._creationTime = response.creationTime; | ||||
|         this._user = response.user; | ||||
|         this._safety = response.safety; | ||||
|         this._contentUrl = response.contentUrl; | ||||
|         this._thumbnailUrl = response.thumbnailUrl; | ||||
|         this._canvasWidth = response.canvasWidth; | ||||
|         this._canvasHeight = response.canvasHeight; | ||||
|         this._fileSize = response.fileSize; | ||||
| 
 | ||||
|         this._tags = response.tags; | ||||
|         this._notes = response.notes; | ||||
|         this._comments = CommentList.fromResponse(response.comments); | ||||
|         this._relations = response.relations; | ||||
| 
 | ||||
|         this._score = response.score; | ||||
|         this._favoriteCount = response.favoriteCount; | ||||
|         this._ownScore = response.ownScore; | ||||
|         this._ownFavorite = response.ownFavorite; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										33
									
								
								client/js/models/post_list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								client/js/models/post_list.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const events = require('../events.js'); | ||||
| const Post = require('./post.js'); | ||||
| 
 | ||||
| class PostList extends events.EventTarget { | ||||
|     constructor(posts) { | ||||
|         super(); | ||||
|         this._list = []; | ||||
|     } | ||||
| 
 | ||||
|     static fromResponse(postsResponse) { | ||||
|         const postList = new PostList(); | ||||
|         for (let postResponse of postsResponse) { | ||||
|             postList._list.push(Post.fromResponse(postResponse)); | ||||
|         } | ||||
|         return postList; | ||||
|     } | ||||
| 
 | ||||
|     get posts() { | ||||
|         return [...this._list]; | ||||
|     } | ||||
| 
 | ||||
|     get length() { | ||||
|         return this._list.length; | ||||
|     } | ||||
| 
 | ||||
|     [Symbol.iterator]() { | ||||
|         return this._list[Symbol.iterator](); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = PostList; | ||||
| @ -1,24 +1,27 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const events = require('../events.js'); | ||||
| const views = require('../util/views.js'); | ||||
| const CommentListControl = require('../controls/comment_list_control.js'); | ||||
| 
 | ||||
| const template = views.getTemplate('comments-page'); | ||||
| 
 | ||||
| class CommentsPageView { | ||||
| class CommentsPageView extends events.EventTarget { | ||||
|     constructor(ctx) { | ||||
|         super(); | ||||
|         this._hostNode = ctx.hostNode; | ||||
|         this._controls = []; | ||||
| 
 | ||||
|         const sourceNode = template(ctx); | ||||
| 
 | ||||
|         for (let post of ctx.results) { | ||||
|             post.comments.sort((a, b) => { return b.id - a.id; }); | ||||
|             this._controls.push( | ||||
|                 new CommentListControl( | ||||
|                     sourceNode.querySelector( | ||||
|                         `.comments-container[data-for="${post.id}"]`), | ||||
|                     post.comments)); | ||||
|             const commentListControl = new CommentListControl( | ||||
|                 sourceNode.querySelector( | ||||
|                     `.comments-container[data-for="${post.id}"]`), | ||||
|                 post.comments, | ||||
|                 true); | ||||
|             events.proxyEvent(commentListControl, this, 'change'); | ||||
|             events.proxyEvent(commentListControl, this, 'score'); | ||||
|             events.proxyEvent(commentListControl, this, 'delete'); | ||||
|         } | ||||
| 
 | ||||
|         views.replaceContent(this._hostNode, sourceNode); | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const api = require('../api.js'); | ||||
| const router = require('../router.js'); | ||||
| const views = require('../util/views.js'); | ||||
| const keyboard = require('../util/keyboard.js'); | ||||
| @ -52,8 +51,8 @@ class PostView { | ||||
|             ctx.post); | ||||
| 
 | ||||
|         this._installSidebar(ctx); | ||||
|         this._installCommentForm(ctx); | ||||
|         this._installComments(ctx); | ||||
|         this._installCommentForm(); | ||||
|         this._installComments(ctx.post.comments); | ||||
| 
 | ||||
|         keyboard.bind('e', () => { | ||||
|             if (ctx.editMode) { | ||||
| @ -79,49 +78,35 @@ class PostView { | ||||
|             '#content-holder .sidebar-container'); | ||||
| 
 | ||||
|         if (ctx.editMode) { | ||||
|             new PostEditSidebarControl( | ||||
|             this.sidebarControl = new PostEditSidebarControl( | ||||
|                 sidebarContainerNode, ctx.post, this._postContentControl); | ||||
|         } else { | ||||
|             new PostReadonlySidebarControl( | ||||
|             this.sidebarControl = new PostReadonlySidebarControl( | ||||
|                 sidebarContainerNode, ctx.post, this._postContentControl); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _installCommentForm(ctx) { | ||||
|     _installCommentForm() { | ||||
|         const commentFormContainer = document.querySelector( | ||||
|             '#content-holder .comment-form-container'); | ||||
|         if (!commentFormContainer) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this._formControl = new CommentFormControl( | ||||
|             commentFormContainer, | ||||
|             null, | ||||
|             { | ||||
|                 onSave: text => { | ||||
|                     return api.post('/comments', { | ||||
|                         postId: ctx.post.id, | ||||
|                         text: text, | ||||
|                     }).then(response => { | ||||
|                         ctx.post.comments.push(response); | ||||
|                         this._formControl.setText(''); | ||||
|                         this._installComments(ctx); | ||||
|                     }, response => { | ||||
|                         this._formControl.showError(response.description); | ||||
|                     }); | ||||
|                 }, | ||||
|                 canCancel: false, | ||||
|                 minHeight: 150, | ||||
|             }); | ||||
|         this._formControl.enterEditMode(); | ||||
|         this.commentFormControl = new CommentFormControl( | ||||
|             commentFormContainer, null, false, 150); | ||||
|         this.commentFormControl.enterEditMode(); | ||||
|     } | ||||
| 
 | ||||
|     _installComments(ctx) { | ||||
|     _installComments(comments) { | ||||
|         const commentsContainerNode = document.querySelector( | ||||
|             '#content-holder .comments-container'); | ||||
|         if (commentsContainerNode) { | ||||
|             new CommentListControl(commentsContainerNode, ctx.post.comments); | ||||
|         if (!commentsContainerNode) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.commentListControl = new CommentListControl( | ||||
|             commentsContainerNode, comments); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 rr-
						rr-