]> BookStack Code Mirror - bookstack/blob - resources/js/components/page-comments.js
8f023836b090876360c120df95e3c1f0a137bf18
[bookstack] / resources / js / components / page-comments.js
1 import {Component} from './component';
2 import {getLoading, htmlToDom} from '../services/dom.ts';
3 import {buildForInput} from '../wysiwyg-tinymce/config';
4
5 export class PageComments extends Component {
6
7     setup() {
8         this.elem = this.$el;
9         this.pageId = Number(this.$opts.pageId);
10
11         // Element references
12         this.container = this.$refs.commentContainer;
13         this.commentCountBar = this.$refs.commentCountBar;
14         this.commentsTitle = this.$refs.commentsTitle;
15         this.addButtonContainer = this.$refs.addButtonContainer;
16         this.replyToRow = this.$refs.replyToRow;
17         this.formContainer = this.$refs.formContainer;
18         this.form = this.$refs.form;
19         this.formInput = this.$refs.formInput;
20         this.formReplyLink = this.$refs.formReplyLink;
21         this.addCommentButton = this.$refs.addCommentButton;
22         this.hideFormButton = this.$refs.hideFormButton;
23         this.removeReplyToButton = this.$refs.removeReplyToButton;
24
25         // WYSIWYG options
26         this.wysiwygLanguage = this.$opts.wysiwygLanguage;
27         this.wysiwygTextDirection = this.$opts.wysiwygTextDirection;
28         this.wysiwygEditor = null;
29
30         // Translations
31         this.createdText = this.$opts.createdText;
32         this.countText = this.$opts.countText;
33
34         // Internal State
35         this.parentId = null;
36         this.formReplyText = this.formReplyLink?.textContent || '';
37
38         this.setupListeners();
39     }
40
41     setupListeners() {
42         this.elem.addEventListener('page-comment-delete', () => {
43             setTimeout(() => this.updateCount(), 1);
44             this.hideForm();
45         });
46
47         this.elem.addEventListener('page-comment-reply', event => {
48             this.setReply(event.detail.id, event.detail.element);
49         });
50
51         if (this.form) {
52             this.removeReplyToButton.addEventListener('click', this.removeReplyTo.bind(this));
53             this.hideFormButton.addEventListener('click', this.hideForm.bind(this));
54             this.addCommentButton.addEventListener('click', this.showForm.bind(this));
55             this.form.addEventListener('submit', this.saveComment.bind(this));
56         }
57     }
58
59     saveComment(event) {
60         event.preventDefault();
61         event.stopPropagation();
62
63         const loading = getLoading();
64         loading.classList.add('px-l');
65         this.form.after(loading);
66         this.form.toggleAttribute('hidden', true);
67
68         const reqData = {
69             html: this.wysiwygEditor.getContent(),
70             parent_id: this.parentId || null,
71         };
72
73         window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => {
74             const newElem = htmlToDom(resp.data);
75
76             if (reqData.parent_id) {
77                 this.formContainer.after(newElem);
78             } else {
79                 this.container.append(newElem);
80             }
81
82             window.$events.success(this.createdText);
83             this.hideForm();
84             this.updateCount();
85         }).catch(err => {
86             this.form.toggleAttribute('hidden', false);
87             window.$events.showValidationErrors(err);
88         });
89
90         this.form.toggleAttribute('hidden', false);
91         loading.remove();
92     }
93
94     updateCount() {
95         const count = this.getCommentCount();
96         this.commentsTitle.textContent = window.$trans.choice(this.countText, count, {count});
97     }
98
99     resetForm() {
100         this.removeEditor();
101         this.formInput.value = '';
102         this.parentId = null;
103         this.replyToRow.toggleAttribute('hidden', true);
104         this.container.append(this.formContainer);
105     }
106
107     showForm() {
108         this.removeEditor();
109         this.formContainer.toggleAttribute('hidden', false);
110         this.addButtonContainer.toggleAttribute('hidden', true);
111         this.formContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'});
112         this.loadEditor();
113     }
114
115     hideForm() {
116         this.resetForm();
117         this.formContainer.toggleAttribute('hidden', true);
118         if (this.getCommentCount() > 0) {
119             this.elem.append(this.addButtonContainer);
120         } else {
121             this.commentCountBar.append(this.addButtonContainer);
122         }
123         this.addButtonContainer.toggleAttribute('hidden', false);
124     }
125
126     loadEditor() {
127         if (this.wysiwygEditor) {
128             this.wysiwygEditor.focus();
129             return;
130         }
131
132         const config = buildForInput({
133             language: this.wysiwygLanguage,
134             containerElement: this.formInput,
135             darkMode: document.documentElement.classList.contains('dark-mode'),
136             textDirection: this.wysiwygTextDirection,
137             translations: {},
138             translationMap: window.editor_translations,
139         });
140
141         window.tinymce.init(config).then(editors => {
142             this.wysiwygEditor = editors[0];
143             setTimeout(() => this.wysiwygEditor.focus(), 50);
144         });
145     }
146
147     removeEditor() {
148         if (this.wysiwygEditor) {
149             this.wysiwygEditor.remove();
150             this.wysiwygEditor = null;
151         }
152     }
153
154     getCommentCount() {
155         return this.container.querySelectorAll('[component="page-comment"]').length;
156     }
157
158     setReply(commentLocalId, commentElement) {
159         const targetFormLocation = commentElement.closest('.comment-branch').querySelector('.comment-branch-children');
160         targetFormLocation.append(this.formContainer);
161         this.showForm();
162         this.parentId = commentLocalId;
163         this.replyToRow.toggleAttribute('hidden', false);
164         this.formReplyLink.textContent = this.formReplyText.replace('1234', this.parentId);
165         this.formReplyLink.href = `#comment${this.parentId}`;
166     }
167
168     removeReplyTo() {
169         this.parentId = null;
170         this.replyToRow.toggleAttribute('hidden', true);
171         this.container.append(this.formContainer);
172         this.showForm();
173     }
174
175 }