mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-07 04:22:06 +03:00
Fixes issue of tabs jumping back to active comments when stopping a reply to an archived comment. Fixes button placement looking odd due to wrong location and differing styles depending on interaction path.
268 lines
10 KiB
TypeScript
268 lines
10 KiB
TypeScript
import {Component} from './component';
|
|
import {getLoading, htmlToDom} from '../services/dom';
|
|
import {buildForInput} from '../wysiwyg-tinymce/config';
|
|
import {Tabs} from "./tabs";
|
|
import {PageCommentReference} from "./page-comment-reference";
|
|
import {scrollAndHighlightElement} from "../services/util";
|
|
import {PageCommentArchiveEventData, PageCommentReplyEventData} from "./page-comment";
|
|
|
|
export class PageComments extends Component {
|
|
|
|
private elem!: HTMLElement;
|
|
private pageId!: number;
|
|
private container!: HTMLElement;
|
|
private commentCountBar!: HTMLElement;
|
|
private activeTab!: HTMLElement;
|
|
private archivedTab!: HTMLElement;
|
|
private addButtonContainer!: HTMLElement;
|
|
private archiveContainer!: HTMLElement;
|
|
private activeContainer!: HTMLElement;
|
|
private replyToRow!: HTMLElement;
|
|
private referenceRow!: HTMLElement;
|
|
private formContainer!: HTMLElement;
|
|
private form!: HTMLFormElement;
|
|
private formInput!: HTMLInputElement;
|
|
private formReplyLink!: HTMLAnchorElement;
|
|
private formReferenceLink!: HTMLAnchorElement;
|
|
private addCommentButton!: HTMLElement;
|
|
private hideFormButton!: HTMLElement;
|
|
private removeReplyToButton!: HTMLElement;
|
|
private removeReferenceButton!: HTMLElement;
|
|
private wysiwygLanguage!: string;
|
|
private wysiwygTextDirection!: string;
|
|
private wysiwygEditor: any = null;
|
|
private createdText!: string;
|
|
private countText!: string;
|
|
private archivedCountText!: string;
|
|
private parentId: number | null = null;
|
|
private contentReference: string = '';
|
|
private formReplyText: string = '';
|
|
|
|
setup() {
|
|
this.elem = this.$el;
|
|
this.pageId = Number(this.$opts.pageId);
|
|
|
|
// Element references
|
|
this.container = this.$refs.commentContainer;
|
|
this.commentCountBar = this.$refs.commentCountBar;
|
|
this.activeTab = this.$refs.activeTab;
|
|
this.archivedTab = this.$refs.archivedTab;
|
|
this.addButtonContainer = this.$refs.addButtonContainer;
|
|
this.archiveContainer = this.$refs.archiveContainer;
|
|
this.activeContainer = this.$refs.activeContainer;
|
|
this.replyToRow = this.$refs.replyToRow;
|
|
this.referenceRow = this.$refs.referenceRow;
|
|
this.formContainer = this.$refs.formContainer;
|
|
this.form = this.$refs.form as HTMLFormElement;
|
|
this.formInput = this.$refs.formInput as HTMLInputElement;
|
|
this.formReplyLink = this.$refs.formReplyLink as HTMLAnchorElement;
|
|
this.formReferenceLink = this.$refs.formReferenceLink as HTMLAnchorElement;
|
|
this.addCommentButton = this.$refs.addCommentButton;
|
|
this.hideFormButton = this.$refs.hideFormButton;
|
|
this.removeReplyToButton = this.$refs.removeReplyToButton;
|
|
this.removeReferenceButton = this.$refs.removeReferenceButton;
|
|
|
|
// WYSIWYG options
|
|
this.wysiwygLanguage = this.$opts.wysiwygLanguage;
|
|
this.wysiwygTextDirection = this.$opts.wysiwygTextDirection;
|
|
|
|
// Translations
|
|
this.createdText = this.$opts.createdText;
|
|
this.countText = this.$opts.countText;
|
|
this.archivedCountText = this.$opts.archivedCountText;
|
|
|
|
this.formReplyText = this.formReplyLink?.textContent || '';
|
|
|
|
this.setupListeners();
|
|
}
|
|
|
|
protected setupListeners(): void {
|
|
this.elem.addEventListener('page-comment-delete', () => {
|
|
setTimeout(() => {
|
|
this.updateCount();
|
|
this.hideForm();
|
|
}, 1);
|
|
});
|
|
|
|
this.elem.addEventListener('page-comment-reply', ((event: CustomEvent<PageCommentReplyEventData>) => {
|
|
this.setReply(event.detail.id, event.detail.element);
|
|
}) as EventListener);
|
|
|
|
this.elem.addEventListener('page-comment-archive', ((event: CustomEvent<PageCommentArchiveEventData>) => {
|
|
this.archiveContainer.append(event.detail.new_thread_dom);
|
|
setTimeout(() => this.updateCount(), 1);
|
|
}) as EventListener);
|
|
|
|
this.elem.addEventListener('page-comment-unarchive', ((event: CustomEvent<PageCommentArchiveEventData>) => {
|
|
this.container.append(event.detail.new_thread_dom);
|
|
setTimeout(() => this.updateCount(), 1);
|
|
}) as EventListener);
|
|
|
|
if (this.form) {
|
|
this.removeReplyToButton.addEventListener('click', this.removeReplyTo.bind(this));
|
|
this.removeReferenceButton.addEventListener('click', () => this.setContentReference(''));
|
|
this.hideFormButton.addEventListener('click', this.hideForm.bind(this));
|
|
this.addCommentButton.addEventListener('click', this.showForm.bind(this));
|
|
this.form.addEventListener('submit', this.saveComment.bind(this));
|
|
}
|
|
}
|
|
|
|
protected saveComment(event: SubmitEvent): void {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const loading = getLoading();
|
|
loading.classList.add('px-l');
|
|
this.form.after(loading);
|
|
this.form.toggleAttribute('hidden', true);
|
|
|
|
const reqData = {
|
|
html: this.wysiwygEditor.getContent(),
|
|
parent_id: this.parentId || null,
|
|
content_ref: this.contentReference,
|
|
};
|
|
|
|
window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => {
|
|
const newElem = htmlToDom(resp.data as string);
|
|
|
|
if (reqData.parent_id) {
|
|
this.formContainer.after(newElem);
|
|
} else {
|
|
this.container.append(newElem);
|
|
}
|
|
|
|
const refs = window.$components.allWithinElement<PageCommentReference>(newElem, 'page-comment-reference');
|
|
for (const ref of refs) {
|
|
ref.showForDisplay();
|
|
}
|
|
|
|
window.$events.success(this.createdText);
|
|
this.hideForm();
|
|
this.updateCount();
|
|
}).catch(err => {
|
|
this.form.toggleAttribute('hidden', false);
|
|
window.$events.showValidationErrors(err);
|
|
});
|
|
|
|
this.form.toggleAttribute('hidden', false);
|
|
loading.remove();
|
|
}
|
|
|
|
protected updateCount(): void {
|
|
const activeCount = this.getActiveThreadCount();
|
|
this.activeTab.textContent = window.$trans.choice(this.countText, activeCount);
|
|
const archivedCount = this.getArchivedThreadCount();
|
|
this.archivedTab.textContent = window.$trans.choice(this.archivedCountText, archivedCount);
|
|
}
|
|
|
|
protected resetForm(): void {
|
|
this.removeEditor();
|
|
this.formInput.value = '';
|
|
this.parentId = null;
|
|
this.replyToRow.toggleAttribute('hidden', true);
|
|
this.container.append(this.formContainer);
|
|
this.setContentReference('');
|
|
}
|
|
|
|
protected showForm(): void {
|
|
this.removeEditor();
|
|
this.formContainer.toggleAttribute('hidden', false);
|
|
this.addButtonContainer.toggleAttribute('hidden', true);
|
|
this.formContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'});
|
|
this.loadEditor();
|
|
|
|
// Ensure the active comments tab is displaying if that's where we're showing the form
|
|
const tabs = window.$components.firstOnElement(this.elem, 'tabs');
|
|
if (tabs instanceof Tabs && this.formContainer.closest('#comment-tab-panel-active')) {
|
|
tabs.show('comment-tab-panel-active');
|
|
}
|
|
}
|
|
|
|
protected hideForm(): void {
|
|
this.resetForm();
|
|
this.formContainer.toggleAttribute('hidden', true);
|
|
if (this.getActiveThreadCount() > 0) {
|
|
this.activeContainer.append(this.addButtonContainer);
|
|
} else {
|
|
this.commentCountBar.append(this.addButtonContainer);
|
|
}
|
|
this.addButtonContainer.toggleAttribute('hidden', false);
|
|
}
|
|
|
|
protected loadEditor(): void {
|
|
if (this.wysiwygEditor) {
|
|
this.wysiwygEditor.focus();
|
|
return;
|
|
}
|
|
|
|
const config = buildForInput({
|
|
language: this.wysiwygLanguage,
|
|
containerElement: this.formInput,
|
|
darkMode: document.documentElement.classList.contains('dark-mode'),
|
|
textDirection: this.wysiwygTextDirection,
|
|
drawioUrl: '',
|
|
pageId: 0,
|
|
translations: {},
|
|
translationMap: (window as unknown as Record<string, Object>).editor_translations,
|
|
});
|
|
|
|
(window as unknown as {tinymce: {init: (arg0: Object) => Promise<any>}}).tinymce.init(config).then(editors => {
|
|
this.wysiwygEditor = editors[0];
|
|
setTimeout(() => this.wysiwygEditor.focus(), 50);
|
|
});
|
|
}
|
|
|
|
protected removeEditor(): void {
|
|
if (this.wysiwygEditor) {
|
|
this.wysiwygEditor.remove();
|
|
this.wysiwygEditor = null;
|
|
}
|
|
}
|
|
|
|
protected getActiveThreadCount(): number {
|
|
return this.container.querySelectorAll(':scope > .comment-branch:not([hidden])').length;
|
|
}
|
|
|
|
protected getArchivedThreadCount(): number {
|
|
return this.archiveContainer.querySelectorAll(':scope > .comment-branch').length;
|
|
}
|
|
|
|
protected setReply(commentLocalId: string, commentElement: HTMLElement): void {
|
|
const targetFormLocation = (commentElement.closest('.comment-branch') as HTMLElement).querySelector('.comment-branch-children') as HTMLElement;
|
|
targetFormLocation.append(this.formContainer);
|
|
this.showForm();
|
|
this.parentId = Number(commentLocalId);
|
|
this.replyToRow.toggleAttribute('hidden', false);
|
|
this.formReplyLink.textContent = this.formReplyText.replace('1234', String(this.parentId));
|
|
this.formReplyLink.href = `#comment${this.parentId}`;
|
|
}
|
|
|
|
protected removeReplyTo(): void {
|
|
this.parentId = null;
|
|
this.replyToRow.toggleAttribute('hidden', true);
|
|
this.container.append(this.formContainer);
|
|
this.showForm();
|
|
}
|
|
|
|
public startNewComment(contentReference: string): void {
|
|
this.resetForm();
|
|
this.showForm();
|
|
this.setContentReference(contentReference);
|
|
}
|
|
|
|
protected setContentReference(reference: string): void {
|
|
this.contentReference = reference;
|
|
this.referenceRow.toggleAttribute('hidden', !Boolean(reference));
|
|
const [id] = reference.split(':');
|
|
this.formReferenceLink.href = `#${id}`;
|
|
this.formReferenceLink.onclick = function(event) {
|
|
event.preventDefault();
|
|
const el = document.getElementById(id);
|
|
if (el) {
|
|
scrollAndHighlightElement(el);
|
|
}
|
|
};
|
|
}
|
|
|
|
}
|