<?php namespace BookStack\Http\Controllers;
+use Activity;
use BookStack\Repos\CommentRepo;
use BookStack\Repos\EntityRepo;
use Illuminate\Database\Eloquent\ModelNotFoundException;
// Create a new comment.
$this->checkPermission('comment-create-all');
- $comment = $this->commentRepo->create($page, $request->all());
+ $comment = $this->commentRepo->create($page, $request->only(['html', 'text', 'parent_id']));
+ Activity::add($page, 'commented_on', $page->book->id);
return view('comments/comment', ['comment' => $comment]);
}
$this->checkOwnablePermission('page-view', $comment->entity);
$this->checkOwnablePermission('comment-update', $comment);
- $comment = $this->commentRepo->update($comment, $request->all());
+ $comment = $this->commentRepo->update($comment, $request->only(['html', 'text']));
return view('comments/comment', ['comment' => $comment]);
}
const MarkdownIt = require("markdown-it");
-const md = new MarkdownIt({ html: true });
+const md = new MarkdownIt({ html: false });
class PageComments {
constructor(elem) {
this.elem = elem;
this.pageId = Number(elem.getAttribute('page-id'));
+ this.editingComment = null;
+ this.parentId = null;
- this.formContainer = elem.querySelector('[comment-form-container]');
- this.form = this.formContainer.querySelector('form');
- this.formInput = this.form.querySelector('textarea');
this.container = elem.querySelector('[comment-container]');
+ this.formContainer = elem.querySelector('[comment-form-container]');
+
+ if (this.formContainer) {
+ this.form = this.formContainer.querySelector('form');
+ this.formInput = this.form.querySelector('textarea');
+ this.form.addEventListener('submit', this.saveComment.bind(this));
+ }
- // TODO - Handle elem usage when no permissions
- this.form.addEventListener('submit', this.saveComment.bind(this));
this.elem.addEventListener('click', this.handleAction.bind(this));
this.elem.addEventListener('submit', this.updateComment.bind(this));
-
- this.editingComment = null;
- this.parentId = null;
}
handleAction(event) {
let actionElem = event.target.closest('[action]');
if (event.target.matches('a[href^="#"]')) {
let id = event.target.href.split('#')[1];
- console.log(document.querySelector('#' + id));
window.scrollAndHighlight(document.querySelector('#' + id));
}
if (actionElem === null) return;
if (this.editingComment) this.closeUpdateForm();
commentElem.querySelector('[comment-content]').style.display = 'none';
commentElem.querySelector('[comment-edit-container]').style.display = 'block';
+ let textArea = commentElem.querySelector('[comment-edit-container] textarea');
+ let lineCount = textArea.value.split('\n').length;
+ textArea.style.height = (lineCount * 20) + 'px';
this.editingComment = commentElem;
}
html: md.render(text),
parent_id: this.parentId || null,
};
- // TODO - Loading indicator
+ this.showLoading(form);
let commentId = this.editingComment.getAttribute('comment');
window.$http.put(window.baseUrl(`/ajax/comment/${commentId}`), reqData).then(resp => {
let newComment = document.createElement('div');
window.components.init(this.editingComment);
this.closeUpdateForm();
this.editingComment = null;
+ this.hideLoading(form);
});
}
deleteComment(commentElem) {
let id = commentElem.getAttribute('comment');
- // TODO - Loading indicator
+ this.showLoading(commentElem.querySelector('[comment-content]'));
window.$http.delete(window.baseUrl(`/ajax/comment/${id}`)).then(resp => {
commentElem.parentNode.removeChild(commentElem);
window.$events.emit('success', window.trans('entities.comment_deleted_success'));
html: md.render(text),
parent_id: this.parentId || null,
};
- // TODO - Loading indicator
+ this.showLoading(this.form);
window.$http.post(window.baseUrl(`/ajax/page/${this.pageId}/comment`), reqData).then(resp => {
let newComment = document.createElement('div');
newComment.innerHTML = resp.data;
this.formContainer.appendChild(this.form);
this.hideForm();
this.removeReplyTo();
+ this.hideLoading(this.form);
}
showForm() {
this.elem.querySelector('[comment-form-reply-to]').style.display = 'none';
}
-}
+ showLoading(formElem) {
+ let groups = formElem.querySelectorAll('.form-group');
+ for (let i = 0, len = groups.length; i < len; i++) {
+ groups[i].style.display = 'none';
+ }
+ formElem.querySelector('.form-group.loading').style.display = 'block';
+ }
-// TODO - Go to comment if url param set
+ hideLoading(formElem) {
+ let groups = formElem.querySelectorAll('.form-group');
+ for (let i = 0, len = groups.length; i < len; i++) {
+ groups[i].style.display = 'block';
+ }
+ formElem.querySelector('.form-group.loading').style.display = 'none';
+ }
+}
module.exports = PageComments;
\ No newline at end of file
+++ /dev/null
-.comment-box {
- border: 1px solid #DDD;
- margin-bottom: $-s;
- border-radius: 3px;
- .content {
- padding: $-s;
- }
- .content p {
- margin-bottom: 1em;
- }
- .reply-row {
- padding: $-xs $-s;
- }
-}
-
-.comment-box .header {
- padding: $-xs $-s;
- background-color: #f8f8f8;
- border-bottom: 1px solid #DDD;
- .meta {
- img, a, span {
- display: inline-block;
- vertical-align: top;
- }
- a, span {
- padding: $-xxs 0 $-xxs 0;
- line-height: 1.6;
- }
- a { color: #666; }
- span {
- color: #888;
- padding-left: $-xxs;
- }
- }
- .text-muted {
- color: #999;
- }
-}
margin-right: $-xs;
text-decoration: underline;
}
+}
+
+.comment-box {
+ border: 1px solid #DDD;
+ margin-bottom: $-s;
+ border-radius: 3px;
+ .content {
+ padding: $-s;
+ font-size: 0.666em;
+ }
+ .content p {
+ font-size: $fs-m;
+ margin: .5em 0;
+ }
+ .reply-row {
+ padding: $-xs $-s;
+ }
+}
+
+.comment-box .header {
+ padding: $-xs $-s;
+ background-color: #f8f8f8;
+ border-bottom: 1px solid #DDD;
+ .meta {
+ img, a, span {
+ display: inline-block;
+ vertical-align: top;
+ }
+ a, span {
+ padding: $-xxs 0 $-xxs 0;
+ line-height: 1.6;
+ }
+ a { color: #666; }
+ span {
+ color: #888;
+ padding-left: $-xxs;
+ }
+ }
+ .text-muted {
+ color: #999;
+ }
}
\ No newline at end of file
@import "header";
@import "lists";
@import "pages";
-@import "comments";
[v-cloak] {
display: none; opacity: 0;
.loading-container {
position: relative;
display: block;
- height: $loadingSize;
margin: $-xl auto;
> div {
width: $loadingSize;
border-radius: $loadingSize;
display: inline-block;
vertical-align: top;
- transform: translate3d(0, 0, 0);
+ transform: translate3d(-10px, 0, 0);
+ margin-top: $-xs;
animation-name: loadingBob;
animation-duration: 1.4s;
animation-iteration-count: infinite;
background-color: $color-book;
animation-delay: 0s;
}
- > div:last-child {
+ > div:last-of-type {
left: $loadingSize+$-xs;
background-color: $color-chapter;
animation-delay: 0.6s;
}
+ > span {
+ margin-left: $-s;
+ font-style: italic;
+ color: #888;
+ vertical-align: top;
+ }
}
'book_sort' => 'sorted book',
'book_sort_notification' => 'Book Successfully Re-sorted',
+ // Other
+ 'commented_on' => 'commented on',
];
'comment_placeholder' => 'Leave a comment here',
'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
'comment_save' => 'Save Comment',
+ 'comment_saving' => 'Saving comment...',
+ 'comment_deleting' => 'Deleting comment...',
'comment_new' => 'New Comment',
'comment_created' => 'commented :createDiff',
'comment_updated' => 'Updated :updateDiff by :username',
-<div class="comment-box" comment="{{ $comment->id }}" local-id="{{$comment->local_id}}" parent-id="{{$comment->parent_id || ''}}" id="comment{{$comment->local_id}}">
+<div class="comment-box" comment="{{ $comment->id }}" local-id="{{$comment->local_id}}" parent-id="{{$comment->parent_id}}" id="comment{{$comment->local_id}}">
<div class="header">
<div class="float right actions">
<div class="meta">
<a href="#comment{{$comment->local_id}}" class="text-muted">#{{$comment->local_id}}</a>
- <img width="50" src="{{ $comment->createdBy->getAvatar(50) }}" class="avatar" alt="{{ $comment->createdBy->name }}">
-
- <a href="{{ $comment->createdBy->getProfileUrl() }}">{{ $comment->createdBy->name }}</a>
- {{--TODO - Account for deleted user--}}
+ @if ($comment->createdBy)
+ <img width="50" src="{{ $comment->createdBy->getAvatar(50) }}" class="avatar" alt="{{ $comment->createdBy->name }}">
+
+ <a href="{{ $comment->createdBy->getProfileUrl() }}">{{ $comment->createdBy->name }}</a>
+ @else
+ <span>{{ trans('common.deleted_user') }}</span>
+ @endif
<span title="{{ $comment->created_at }}">
{{ trans('entities.comment_created', ['createDiff' => $comment->created]) }}
</span>
@if($comment->isUpdated())
<span title="{{ $comment->updated_at }}">
•
- {{ trans('entities.comment_updated', ['updateDiff' => $comment->updated, 'username' => $comment->updatedBy->name]) }}
+ {{ trans('entities.comment_updated', ['updateDiff' => $comment->updated, 'username' => $comment->updatedBy? $comment->updatedBy->name : trans('common.deleted_user')]) }}
</span>
@endif
</div>
@endif
<div comment-content class="content">
+ <div class="form-group loading" style="display: none;">
+ @include('partials.loading-icon', ['text' => trans('entities.comment_deleting')])
+ </div>
{!! $comment->html !!}
</div>
<button type="button" class="button outline" action="closeUpdateForm">{{ trans('common.cancel') }}</button>
<button type="submit" class="button pos">{{ trans('entities.comment_save') }}</button>
</div>
+ <div class="form-group loading" style="display: none;">
+ @include('partials.loading-icon', ['text' => trans('entities.comment_saving')])
+ </div>
</form>
</div>
@endif
<button type="button" class="button outline" action="hideForm">{{ trans('common.cancel') }}</button>
<button type="submit" class="button pos">{{ trans('entities.comment_save') }}</button>
</div>
+ <div class="form-group loading" style="display: none;">
+ @include('partials.loading-icon', ['text' => trans('entities.comment_saving')])
+ </div>
</form>
</div>
</div>
<div></div>
<div></div>
<div></div>
+ @if(isset($text))
+ <span>{{$text}}</span>
+ @endif
</div>
\ No newline at end of file