]> BookStack Code Mirror - bookstack/blob - resources/js/components/page-comments.js
Merge branch 'feature/#1598' of git://github.com/cw1998/BookStack into cw1998-feature...
[bookstack] / resources / js / components / page-comments.js
1 import MarkdownIt from "markdown-it";
2 import {scrollAndHighlightElement} from "../services/util";
3
4 const md = new MarkdownIt({ html: false });
5
6 class PageComments {
7
8     constructor(elem) {
9         this.elem = elem;
10         this.pageId = Number(elem.getAttribute('page-id'));
11         this.editingComment = null;
12         this.parentId = null;
13
14         this.container = elem.querySelector('[comment-container]');
15         this.formContainer = elem.querySelector('[comment-form-container]');
16
17         if (this.formContainer) {
18             this.form = this.formContainer.querySelector('form');
19             this.formInput = this.form.querySelector('textarea');
20             this.form.addEventListener('submit', this.saveComment.bind(this));
21         }
22
23         this.elem.addEventListener('click', this.handleAction.bind(this));
24         this.elem.addEventListener('submit', this.updateComment.bind(this));
25     }
26
27     handleAction(event) {
28         let actionElem = event.target.closest('[action]');
29
30         if (event.target.matches('a[href^="#"]')) {
31             const id = event.target.href.split('#')[1];
32             scrollAndHighlightElement(document.querySelector('#' + id));
33         }
34
35         if (actionElem === null) return;
36         event.preventDefault();
37
38         let action = actionElem.getAttribute('action');
39         if (action === 'edit') this.editComment(actionElem.closest('[comment]'));
40         if (action === 'closeUpdateForm') this.closeUpdateForm();
41         if (action === 'delete') this.deleteComment(actionElem.closest('[comment]'));
42         if (action === 'addComment') this.showForm();
43         if (action === 'hideForm') this.hideForm();
44         if (action === 'reply') this.setReply(actionElem.closest('[comment]'));
45         if (action === 'remove-reply-to') this.removeReplyTo();
46     }
47
48     closeUpdateForm() {
49         if (!this.editingComment) return;
50         this.editingComment.querySelector('[comment-content]').style.display = 'block';
51         this.editingComment.querySelector('[comment-edit-container]').style.display = 'none';
52     }
53
54     editComment(commentElem) {
55         this.hideForm();
56         if (this.editingComment) this.closeUpdateForm();
57         commentElem.querySelector('[comment-content]').style.display = 'none';
58         commentElem.querySelector('[comment-edit-container]').style.display = 'block';
59         let textArea = commentElem.querySelector('[comment-edit-container] textarea');
60         let lineCount = textArea.value.split('\n').length;
61         textArea.style.height = ((lineCount * 20) + 40) + 'px';
62         this.editingComment = commentElem;
63     }
64
65     updateComment(event) {
66         let form = event.target;
67         event.preventDefault();
68         let text = form.querySelector('textarea').value;
69         let reqData = {
70             text: text,
71             html: md.render(text),
72             parent_id: this.parentId || null,
73         };
74         this.showLoading(form);
75         let commentId = this.editingComment.getAttribute('comment');
76         window.$http.put(window.baseUrl(`/ajax/comment/${commentId}`), reqData).then(resp => {
77             let newComment = document.createElement('div');
78             newComment.innerHTML = resp.data;
79             this.editingComment.innerHTML = newComment.children[0].innerHTML;
80             window.$events.emit('success', window.trans('entities.comment_updated_success'));
81             window.components.init(this.editingComment);
82             this.closeUpdateForm();
83             this.editingComment = null;
84             this.hideLoading(form);
85         });
86     }
87
88     deleteComment(commentElem) {
89         let id = commentElem.getAttribute('comment');
90         this.showLoading(commentElem.querySelector('[comment-content]'));
91         window.$http.delete(window.baseUrl(`/ajax/comment/${id}`)).then(resp => {
92             commentElem.parentNode.removeChild(commentElem);
93             window.$events.emit('success', window.trans('entities.comment_deleted_success'));
94             this.updateCount();
95             this.hideForm();
96         });
97     }
98
99     saveComment(event) {
100         event.preventDefault();
101         event.stopPropagation();
102         let text = this.formInput.value;
103         let reqData = {
104             text: text,
105             html: md.render(text),
106             parent_id: this.parentId || null,
107         };
108         this.showLoading(this.form);
109         window.$http.post(window.baseUrl(`/ajax/page/${this.pageId}/comment`), reqData).then(resp => {
110             let newComment = document.createElement('div');
111             newComment.innerHTML = resp.data;
112             let newElem = newComment.children[0];
113             this.container.appendChild(newElem);
114             window.components.init(newElem);
115             window.$events.emit('success', window.trans('entities.comment_created_success'));
116             this.resetForm();
117             this.updateCount();
118         });
119     }
120
121     updateCount() {
122         let count = this.container.children.length;
123         this.elem.querySelector('[comments-title]').textContent = window.trans_choice('entities.comment_count', count, {count});
124     }
125
126     resetForm() {
127         this.formInput.value = '';
128         this.formContainer.appendChild(this.form);
129         this.hideForm();
130         this.removeReplyTo();
131         this.hideLoading(this.form);
132     }
133
134     showForm() {
135         this.formContainer.style.display = 'block';
136         this.formContainer.parentNode.style.display = 'block';
137         this.elem.querySelector('[comment-add-button-container]').style.display = 'none';
138         this.formInput.focus();
139         this.formInput.scrollIntoView({behavior: "smooth"});
140     }
141
142     hideForm() {
143         this.formContainer.style.display = 'none';
144         this.formContainer.parentNode.style.display = 'none';
145         const addButtonContainer = this.elem.querySelector('[comment-add-button-container]');
146         if (this.getCommentCount() > 0) {
147             this.elem.appendChild(addButtonContainer)
148         } else {
149             const countBar = this.elem.querySelector('[comment-count-bar]');
150             countBar.appendChild(addButtonContainer);
151         }
152         addButtonContainer.style.display = 'block';
153     }
154
155     getCommentCount() {
156         return this.elem.querySelectorAll('.comment-box[comment]').length;
157     }
158
159     setReply(commentElem) {
160         this.showForm();
161         this.parentId = Number(commentElem.getAttribute('local-id'));
162         this.elem.querySelector('[comment-form-reply-to]').style.display = 'block';
163         let replyLink = this.elem.querySelector('[comment-form-reply-to] a');
164         replyLink.textContent = `#${this.parentId}`;
165         replyLink.href = `#comment${this.parentId}`;
166     }
167
168     removeReplyTo() {
169         this.parentId = null;
170         this.elem.querySelector('[comment-form-reply-to]').style.display = 'none';
171     }
172
173     showLoading(formElem) {
174         let groups = formElem.querySelectorAll('.form-group');
175         for (let i = 0, len = groups.length; i < len; i++) {
176             groups[i].style.display = 'none';
177         }
178         formElem.querySelector('.form-group.loading').style.display = 'block';
179     }
180
181     hideLoading(formElem) {
182         let groups = formElem.querySelectorAll('.form-group');
183         for (let i = 0, len = groups.length; i < len; i++) {
184             groups[i].style.display = 'block';
185         }
186         formElem.querySelector('.form-group.loading').style.display = 'none';
187     }
188
189 }
190
191 export default PageComments;