X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/add238fe9fb3d3626e8acd323bd32f91edb2797e..refs/pull/5676/head:/resources/js/components/page-comments.ts diff --git a/resources/js/components/page-comments.ts b/resources/js/components/page-comments.ts index a19d2c7d4..a1eeda1f9 100644 --- a/resources/js/components/page-comments.ts +++ b/resources/js/components/page-comments.ts @@ -1,35 +1,39 @@ import {Component} from './component'; -import {getLoading, htmlToDom} from '../services/dom.ts'; -import {buildForInput} from '../wysiwyg-tinymce/config'; - -export interface CommentReplyEvent extends Event { - detail: { - id: string; // ID of comment being replied to - element: HTMLElement; // Container for comment replied to - } -} +import {getLoading, htmlToDom} from '../services/dom'; +import {Tabs} from "./tabs"; +import {PageCommentReference} from "./page-comment-reference"; +import {scrollAndHighlightElement} from "../services/util"; +import {PageCommentArchiveEventData, PageCommentReplyEventData} from "./page-comment"; +import {el} from "../wysiwyg/utils/dom"; +import {SimpleWysiwygEditorInterface} from "../wysiwyg"; export class PageComments extends Component { - private elem: HTMLElement; - private pageId: number; - private container: HTMLElement; - private commentCountBar: HTMLElement; - private commentsTitle: HTMLElement; - private addButtonContainer: HTMLElement; - private replyToRow: HTMLElement; - private formContainer: HTMLElement; - private form: HTMLFormElement; - private formInput: HTMLInputElement; - private formReplyLink: HTMLAnchorElement; - private addCommentButton: HTMLElement; - private hideFormButton: HTMLElement; - private removeReplyToButton: HTMLElement; - private wysiwygLanguage: string; - private wysiwygTextDirection: string; - private wysiwygEditor: any = null; - private createdText: string; - private countText: string; + 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 wysiwygTextDirection!: string; + private wysiwygEditor: SimpleWysiwygEditorInterface|null = null; + private createdText!: string; + private countText!: string; + private archivedCountText!: string; private parentId: number | null = null; private contentReference: string = ''; private formReplyText: string = ''; @@ -41,24 +45,30 @@ export class PageComments extends Component { // Element references this.container = this.$refs.commentContainer; this.commentCountBar = this.$refs.commentCountBar; - this.commentsTitle = this.$refs.commentsTitle; + 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 || ''; @@ -67,23 +77,36 @@ export class PageComments extends Component { protected setupListeners(): void { this.elem.addEventListener('page-comment-delete', () => { - setTimeout(() => this.updateCount(), 1); - this.hideForm(); + setTimeout(() => { + this.updateCount(); + this.hideForm(); + }, 1); }); - this.elem.addEventListener('page-comment-reply', (event: CommentReplyEvent) => { + this.elem.addEventListener('page-comment-reply', ((event: CustomEvent) => { this.setReply(event.detail.id, event.detail.element); - }); + }) as EventListener); + + this.elem.addEventListener('page-comment-archive', ((event: CustomEvent) => { + this.archiveContainer.append(event.detail.new_thread_dom); + setTimeout(() => this.updateCount(), 1); + }) as EventListener); + + this.elem.addEventListener('page-comment-unarchive', ((event: CustomEvent) => { + 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): void { + protected async saveComment(event: SubmitEvent): Promise { event.preventDefault(); event.stopPropagation(); @@ -93,9 +116,9 @@ export class PageComments extends Component { this.form.toggleAttribute('hidden', true); const reqData = { - html: this.wysiwygEditor.getContent(), + html: (await this.wysiwygEditor?.getContentAsHtml()) || '', parent_id: this.parentId || null, - content_reference: this.contentReference || '', + content_ref: this.contentReference, }; window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => { @@ -107,6 +130,11 @@ export class PageComments extends Component { this.container.append(newElem); } + const refs = window.$components.allWithinElement(newElem, 'page-comment-reference'); + for (const ref of refs) { + ref.showForDisplay(); + } + window.$events.success(this.createdText); this.hideForm(); this.updateCount(); @@ -120,17 +148,19 @@ export class PageComments extends Component { } protected updateCount(): void { - const count = this.getCommentCount(); - this.commentsTitle.textContent = window.$trans.choice(this.countText, count); + 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.contentReference = ''; this.replyToRow.toggleAttribute('hidden', true); this.container.append(this.formContainer); + this.setContentReference(''); } protected showForm(): void { @@ -139,40 +169,44 @@ export class PageComments extends Component { 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.getCommentCount() > 0) { - this.elem.append(this.addButtonContainer); + if (this.getActiveThreadCount() > 0) { + this.activeContainer.append(this.addButtonContainer); } else { this.commentCountBar.append(this.addButtonContainer); } this.addButtonContainer.toggleAttribute('hidden', false); } - protected loadEditor(): void { + protected async loadEditor(): Promise { if (this.wysiwygEditor) { this.wysiwygEditor.focus(); return; } - const config = buildForInput({ - language: this.wysiwygLanguage, - containerElement: this.formInput, + type WysiwygModule = typeof import('../wysiwyg'); + const wysiwygModule = (await window.importVersioned('wysiwyg')) as WysiwygModule; + const container = el('div', {class: 'comment-editor-container'}); + this.formInput.parentElement?.appendChild(container); + this.formInput.hidden = true; + + this.wysiwygEditor = wysiwygModule.createBasicEditorInstance(container as HTMLElement, '

', { darkMode: document.documentElement.classList.contains('dark-mode'), textDirection: this.wysiwygTextDirection, - drawioUrl: '', - pageId: 0, - translations: {}, - translationMap: (window as Record).editor_translations, + translations: (window as unknown as Record).editor_translations, }); - (window as {tinymce: {init: (Object) => Promise}}).tinymce.init(config).then(editors => { - this.wysiwygEditor = editors[0]; - setTimeout(() => this.wysiwygEditor.focus(), 50); - }); + this.wysiwygEditor.focus(); } protected removeEditor(): void { @@ -182,15 +216,19 @@ export class PageComments extends Component { } } - protected getCommentCount(): number { - return this.container.querySelectorAll('[component="page-comment"]').length; + 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, commentElement): void { - const targetFormLocation = commentElement.closest('.comment-branch').querySelector('.comment-branch-children'); + 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 = commentLocalId; + 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}`; @@ -204,8 +242,23 @@ export class PageComments extends Component { } public startNewComment(contentReference: string): void { - this.removeReplyTo(); - this.contentReference = contentReference; + 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); + } + }; } }