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