1 import {Component} from './component';
2 import {getLoading, htmlToDom} from '../services/dom.ts';
3 import {buildForInput} from '../wysiwyg-tinymce/config';
4 import {Tabs} from "./tabs";
6 export interface CommentReplyEvent extends Event {
8 id: string; // ID of comment being replied to
9 element: HTMLElement; // Container for comment replied to
13 export interface ArchiveEvent extends Event {
15 new_thread_dom: HTMLElement;
19 export class PageComments extends Component {
21 private elem: HTMLElement;
22 private pageId: number;
23 private container: HTMLElement;
24 private commentCountBar: HTMLElement;
25 private activeTab: HTMLElement;
26 private archivedTab: HTMLElement;
27 private addButtonContainer: HTMLElement;
28 private archiveContainer: HTMLElement;
29 private replyToRow: HTMLElement;
30 private formContainer: HTMLElement;
31 private form: HTMLFormElement;
32 private formInput: HTMLInputElement;
33 private formReplyLink: HTMLAnchorElement;
34 private addCommentButton: HTMLElement;
35 private hideFormButton: HTMLElement;
36 private removeReplyToButton: HTMLElement;
37 private wysiwygLanguage: string;
38 private wysiwygTextDirection: string;
39 private wysiwygEditor: any = null;
40 private createdText: string;
41 private countText: string;
42 private archivedCountText: string;
43 private parentId: number | null = null;
44 private contentReference: string = '';
45 private formReplyText: string = '';
49 this.pageId = Number(this.$opts.pageId);
52 this.container = this.$refs.commentContainer;
53 this.commentCountBar = this.$refs.commentCountBar;
54 this.activeTab = this.$refs.activeTab;
55 this.archivedTab = this.$refs.archivedTab;
56 this.addButtonContainer = this.$refs.addButtonContainer;
57 this.archiveContainer = this.$refs.archiveContainer;
58 this.replyToRow = this.$refs.replyToRow;
59 this.formContainer = this.$refs.formContainer;
60 this.form = this.$refs.form as HTMLFormElement;
61 this.formInput = this.$refs.formInput as HTMLInputElement;
62 this.formReplyLink = this.$refs.formReplyLink as HTMLAnchorElement;
63 this.addCommentButton = this.$refs.addCommentButton;
64 this.hideFormButton = this.$refs.hideFormButton;
65 this.removeReplyToButton = this.$refs.removeReplyToButton;
68 this.wysiwygLanguage = this.$opts.wysiwygLanguage;
69 this.wysiwygTextDirection = this.$opts.wysiwygTextDirection;
72 this.createdText = this.$opts.createdText;
73 this.countText = this.$opts.countText;
74 this.archivedCountText = this.$opts.archivedCountText;
76 this.formReplyText = this.formReplyLink?.textContent || '';
78 this.setupListeners();
81 protected setupListeners(): void {
82 this.elem.addEventListener('page-comment-delete', () => {
83 setTimeout(() => this.updateCount(), 1);
87 this.elem.addEventListener('page-comment-reply', (event: CommentReplyEvent) => {
88 this.setReply(event.detail.id, event.detail.element);
91 this.elem.addEventListener('page-comment-archive', (event: ArchiveEvent) => {
92 this.archiveContainer.append(event.detail.new_thread_dom);
93 setTimeout(() => this.updateCount(), 1);
96 this.elem.addEventListener('page-comment-unarchive', (event: ArchiveEvent) => {
97 this.container.append(event.detail.new_thread_dom);
98 setTimeout(() => this.updateCount(), 1);
102 this.removeReplyToButton.addEventListener('click', this.removeReplyTo.bind(this));
103 this.hideFormButton.addEventListener('click', this.hideForm.bind(this));
104 this.addCommentButton.addEventListener('click', this.showForm.bind(this));
105 this.form.addEventListener('submit', this.saveComment.bind(this));
109 protected saveComment(event): void {
110 event.preventDefault();
111 event.stopPropagation();
113 const loading = getLoading();
114 loading.classList.add('px-l');
115 this.form.after(loading);
116 this.form.toggleAttribute('hidden', true);
119 html: this.wysiwygEditor.getContent(),
120 parent_id: this.parentId || null,
121 content_ref: this.contentReference || '',
124 window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => {
125 const newElem = htmlToDom(resp.data as string);
127 if (reqData.parent_id) {
128 this.formContainer.after(newElem);
130 this.container.append(newElem);
133 window.$events.success(this.createdText);
137 this.form.toggleAttribute('hidden', false);
138 window.$events.showValidationErrors(err);
141 this.form.toggleAttribute('hidden', false);
145 protected updateCount(): void {
146 const activeCount = this.getActiveThreadCount();
147 this.activeTab.textContent = window.$trans.choice(this.countText, activeCount);
148 const archivedCount = this.getArchivedThreadCount();
149 this.archivedTab.textContent = window.$trans.choice(this.archivedCountText, archivedCount);
152 protected resetForm(): void {
154 this.formInput.value = '';
155 this.parentId = null;
156 this.contentReference = '';
157 this.replyToRow.toggleAttribute('hidden', true);
158 this.container.append(this.formContainer);
161 protected showForm(): void {
163 this.formContainer.toggleAttribute('hidden', false);
164 this.addButtonContainer.toggleAttribute('hidden', true);
165 this.formContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'});
168 // Ensure the active comments tab is displaying
169 const tabs = window.$components.firstOnElement(this.elem, 'tabs');
170 if (tabs instanceof Tabs) {
171 tabs.show('comment-tab-panel-active');
175 protected hideForm(): void {
177 this.formContainer.toggleAttribute('hidden', true);
178 if (this.getActiveThreadCount() > 0) {
179 this.elem.append(this.addButtonContainer);
181 this.commentCountBar.append(this.addButtonContainer);
183 this.addButtonContainer.toggleAttribute('hidden', false);
186 protected loadEditor(): void {
187 if (this.wysiwygEditor) {
188 this.wysiwygEditor.focus();
192 const config = buildForInput({
193 language: this.wysiwygLanguage,
194 containerElement: this.formInput,
195 darkMode: document.documentElement.classList.contains('dark-mode'),
196 textDirection: this.wysiwygTextDirection,
200 translationMap: (window as Record<string, Object>).editor_translations,
203 (window as {tinymce: {init: (Object) => Promise<any>}}).tinymce.init(config).then(editors => {
204 this.wysiwygEditor = editors[0];
205 setTimeout(() => this.wysiwygEditor.focus(), 50);
209 protected removeEditor(): void {
210 if (this.wysiwygEditor) {
211 this.wysiwygEditor.remove();
212 this.wysiwygEditor = null;
216 protected getActiveThreadCount(): number {
217 return this.container.querySelectorAll(':scope > .comment-branch:not([hidden])').length;
220 protected getArchivedThreadCount(): number {
221 return this.archiveContainer.querySelectorAll(':scope > .comment-branch').length;
224 protected setReply(commentLocalId, commentElement): void {
225 const targetFormLocation = commentElement.closest('.comment-branch').querySelector('.comment-branch-children');
226 targetFormLocation.append(this.formContainer);
228 this.parentId = commentLocalId;
229 this.replyToRow.toggleAttribute('hidden', false);
230 this.formReplyLink.textContent = this.formReplyText.replace('1234', String(this.parentId));
231 this.formReplyLink.href = `#comment${this.parentId}`;
234 protected removeReplyTo(): void {
235 this.parentId = null;
236 this.replyToRow.toggleAttribute('hidden', true);
237 this.container.append(this.formContainer);
241 public startNewComment(contentReference: string): void {
242 this.removeReplyTo();
243 this.contentReference = contentReference;