// steps: // اضافه کردن ویژگی های زیر به تگ های والد. // 1- :ref="'section-' + propertyName" // 2- :id="'section-' + propertyName" // 3- @mouseup="onMouseUp" // اضافه کردن کامپوننت های JahatCommentsForm و JahatCommentsList به صفحه import tahrirApis from "~/apis/tahrirApi"; // import tinyTahrir from "assets/tahrir/vendors/tinymce-files/tinytahrir"; import HttpService from "~/services/httpService"; import repoApi from "~/apis/repoApi"; export default { created() { window.addEventListener("scroll", this.handleScroll); }, beforeMount() { this.commentHttpService = new HttpService( import.meta.env.VITE_TAHRIR_BASE_URL ); this.httpService1 = new HttpService(import.meta.env.VITE_MESSAGE_BASE_URL); this.id = this.$route.params.id; }, destroyed() { window.removeEventListener("scroll", this.handleScroll); }, data() { return { id: "", comments: [], newComment: false, selectedText: null, globalGuid: undefined, paragraphId: undefined, paragraphParentId: undefined, commentHttpService: undefined, httpService1: undefined, }; }, methods: { /* open comment form when user select a text */ onMouseUp($event) { // console.log($event.target.id) // Get selected text and encode it const selection = encodeURIComponent( this.getSelected().toString() ).replace(/[!'()*]/g); if (selection) { this.selectedText = selection; this.setParagraphId($event); this.createPopup($event, "linkPopup"); } }, /* extract user selected text */ getSelected() { if (window.getSelection) { return window.getSelection(); } else if (document.getSelection) { return document.getSelection(); } else { var selection = document.selection && document.selection.createRange(); if (selection.text) { return selection.text; } return false; } }, setParagraphId(ev) { this.paragraphId = ev.target.id; this.paragraphParentId = ev.target.parentNode?.id; }, /* create comment popup and place it on top fo the selected text; */ createPopup(event) { this.openCommentForm(); // Get cursor position // const posX = event.clientX - 130; // const posY = event.clientY - 200; const posX = event.clientX; const posY = event.clientY; try { this.$refs.popupMenu.$el.style.top = posY + "px"; this.$refs.popupMenu.$el.style.left = posX + "px"; this.$refs.popupMenu.$el.style.opacity = 1; } catch (err) { setTimeout(() => { this.$refs.popupMenu.$el.style.top = posY + "px"; this.$refs.popupMenu.$el.style.left = posX + "px"; this.$refs.popupMenu.$el.style.opacity = 1; }, 700); } }, openCommentForm() { this.newComment = true; }, /* summary: save selected text description: replace selected text with the new span.tcomment tag and then save it. send a new request to api for saving the comment. then update/replace paragraph content with newely created content. then crawle all paragraphs for .tcomment ids. then request api for paragraphs comments. @fires when user clicked on comment popup save button. @param {userComment} Html user comment. @return void. */ saveSelectedTextComment(userComment) { const url = tahrirApis.comments.addCommentToSelectedParag; const replacedContent = this.replaceSelectedText(); const payload = { content: replacedContent, guid: this.paragraphId, }; this.commentHttpService .formDataRequest(url, payload) .then((res) => { this.newComment = false; this.addComment( { text: userComment, pid: this.globalGuid, }, replacedContent ); }) .catch((err) => { this.mySwalToast({ html: err?.message, }); }) .finally(() => (this.loading = false)); }, /* summary: save comment description: send a new request to api for saving the comment. then update/replace paragraph content with newely created content. then crawle all paragraphs for .tcomment ids. then request api for paragraphs comments. @fires when saveSelectedTextComment method completed... @param {text} String user comment. @param {pid} String globalGuid. @param {replacedContent} Html user comment. @return void. */ addComment({ text, pid }, replacedContent) { const payload = { text: JSON.stringify(text) }; const url = tahrirApis.comments.add + "/" + pid; this.commentHttpService.formDataRequest(url, payload).then((res) => { this.updateParagraphContentProperty(replacedContent); this.crawlParagsForCommentId(this.localParagraphs).then((guidList) => { this.getComments(guidList); }); this.mySwalToast({ html: res.message, }); }); }, /* summary: update paragraph content property description: update paragraph old content property with updated content. @fires when addComment method completed... @param {replacedContent} Html user comment. @return void. */ updateParagraphContentProperty(replacedContent) { this.localParagraphs.forEach((pars) => { if (pars.id == this.paragraphParentId.slice(7)) pars.content = replacedContent; }); }, /* summary: update new comment form position description: update position of new comment form when scrolling. @fires when user scroll @param {event} event window scroll event. @return void. */ updateCommentFormTopPosition(event) { // Find out how much (if any) user has scrolled var scrollTop = window.screenY !== undefined ? window.screenY : ( document.documentElement || document.body.parentNode || document.body ).scrollTop; const posY = event.clientY - 200 + scrollTop; this.$refs.popupMenu._vnode.elm.style.top = posY + "px"; }, /* summary: comment list description: getting list of comments from the api. @fires after page crawl for tcomment complete. @param {array} guidList paragraph's comment ids. @return void. */ getComments(guidList) { let payload = { limit: 50, offset: 0, refrence_id: this.$route.params.id, entity_type_id: this.$route.meta.entityType, entity_field_id: 1, }; let url = repoApi.messages.update; this.httpService1 .postRequest(url, payload) .then((res) => { this.mySwalToast({ html: res.message, }); this.comments = res.data; }) .catch((err) => { this.mySwalToast({ html: err?.message, }); }) .finally(() => {}); // let conversation = { // limit: 50, // offset: 0, // refrence_id: this.id, // }; // let url = repoApi.messages.list; // this.httpService1.formDataRequest(url, conversation).then((res) => { // this.comments=res.data // if (res.data) { // this.attachCommentsToParags(res.data); // } // }); // const payload = { // pids: guidList, // }; // this.commentHttpService.formDataRequest(tahrirApis.comments.documentComment, payload).then( // (res) => { // if (res.data) // this.attachCommentsToParags(res.data); // } // ); }, /* summary: attach comments to paragraphs object description: loop over paragraphs and find paragraphs that its content property math the comment id catched from the api. @fires after getComment method called. @param {object} @return void. */ attachCommentsToParags(comments) { try { this.localParagraphs.forEach((parag) => { const commentList = []; Object.keys(comments).forEach((value) => { if (parag.content.includes(value)) commentList.push(comments[value]); }); this.$set(parag, "comments", commentList); }); } catch (err) { this.comments = comments; } this.fetchingData = false; }, /* summary: replace selected text with new span. description: find the parent paragraph with its refs and then replace the selected text with new span. @fires when saveSelectedTextComment method called. @return Html. */ replaceSelectedText() { const text = decodeURIComponent(this.selectedText); this.globalGuid = this.newGuid(); const replacementTag = `${text}`; const refContent = this.$refs[this.paragraphParentId][0]; refContent.innerHTML = refContent.innerHTML.replace(text, replacementTag); return refContent.innerHTML; }, closeCommentForm() { this.newComment = false; }, /* summary: crawl paragraphs for .tcomment. description: crawl paragraph fot .tcomment tags and getting its id. @fires when addComment method called. @return Html. */ async crawlParagsForCommentId(pars) { return await pars .map((par) => { if (par?.content.includes("tcomment")) return this.geCommenttIds(par.content); }) .filter((item) => item) .join(","); }, /* summary: create unique id. @fires when crawlParagsForCommentId method called. @return string. */ newGuid() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function (c) { var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8; return v.toString(16); } ); }, /* summary: separate spans id property.. @fires when crawlParagsForCommentId method called. @return string. */ geCommenttIds(content) { var ids = ""; var regex = /\/g; var m = regex.exec(content); while (m !== null) { if (m.index === regex.lastIndex) { regex.lastIndex++; } if (ids != "") ids += ","; ids += m[1]; m = regex.exec(content); } return ids; }, handleScroll(event) { // Any code to be executed when the window is scrolled }, /* summary: hide/show other paragraphs comments. description: when user clicked on comment, hide other comments and add active class to .tcomment tag in the paragraph @fires when user open/close the comments. @param {object} paragraph @param {Number} commentIndex @return void. */ hideOtherComments({ paragraph, commentIndex }) { const _this = this; _this.localParagraphs.forEach((par) => { // parse other paragraphs if (par.id != paragraph.id) { if (par.comments?.length) { par.comments.forEach((com, index) => { const res = com.isShow == undefined ? false : !com.isShow; _this.$set(par.comments[index], "isShow", res); }); } } // parse selected paragraph else { if (par.comments?.length) { par.comments.forEach((com, index) => { // hide sibling comments in the active paragraph. if (index != commentIndex) { const res = com.isShow == undefined ? false : !com.isShow; _this.$set(par.comments[index], "isShow", res); } // toggle paragraph .tcomment class else { Array.from( _this.$refs["parent-" + par.id][0].children[0].children ).forEach((child) => { if (child.id == com[0].pid) { child.classList.toggle("active"); } }); } }); } } }); }, removSpan(comment) { const spanTag = document.getElementsById(comment.pid); const rawText = spanTag.innerHTML; this.paragraph.content = this.paragraph.content.replace(spanTag, rawText); return this.paragraph.content; }, saveChangeSelectedTextComment(userComment) { const url = tahrirApis.comments.addCommentToSelectedParag; const replacedContent = this.removSpan(); const payload = { content: replacedContent, guid: this.paragraphId, }; ApiService.formData(url, payload) .then((res) => { this.newComment = false; this.addComment( { text: userComment, pid: this.globalGuid, }, replacedContent ); }) .catch((err) => { this.mySwalToast({ html: err?.message, }); }) .finally(() => (this.loading = false)); }, // #region comments link form methods replaceSelectedText(linkText) { const text = decodeURIComponent(this.selectedText); this.globalGuid = this.newGuid(); const replacementTag = `${text}`; const refContent = this.parentEl; refContent.innerHTML = refContent.innerHTML.replace(text, replacementTag); const sectionContent = refContent.innerHTML; return sectionContent; }, getSelectionParentElement() { var parentEl = null, sel; if (window.getSelection) { sel = window.getSelection(); if (sel.rangeCount) { parentEl = sel.getRangeAt(0).commonAncestorContainer; if (parentEl.nodeType != 1) { parentEl = parentEl.parentNode; } } } else if ((sel = document.selection) && sel.type != "Control") { parentEl = sel.createRange().parentElement(); } return parentEl; }, saveLink(linkText = null) { //چون از جهت کپی شده ، اینجا خطا دارد و تست و بازبینی باید بشود let parnetId = this.parentEl.id; let keyName = parnetId.replace("section-", ""); const formData = { [keyName]: this.replaceSelectedText(linkText), }; let url = `public/edit/${this.entity?.id}/${keyName}`; this.httpService.postRequest(url, formData).then((response) => {}); }, closeCommentForm() { this.newComment = false; }, createPopup(event, formRefName = "popupMenu") { this.openCommentForm(formRefName); this.parentEl = this.getSelectionParentElement(); const posX = event.clientX; const posY = event.clientY; try { this.$refs[formRefName].$el.style.top = posY + "px"; this.$refs[formRefName].$el.style.left = posX + "px"; this.$refs[formRefName].$el.style.opacity = 1; } catch (err) { setTimeout(() => { this.$refs[formRefName].$el.style.top = posY + "px"; this.$refs[formRefName].$el.style.left = posX + "px"; this.$refs[formRefName].$el.style.opacity = 1; }, 700); } }, openCommentForm(formRefName = "popupMenu") { if (formRefName == "popupMenu") this.newComment = true; else this.newLink = true; }, closeCommentForm(formRefName = "popupMenu") { if (formRefName == "popupMenu") this.newComment = false; else this.newLink = false; }, localSaveSelectedTextComment(userComment) { this.addToConversation(userComment); }, addToConversation(res) { let conversation = { text: res, refrence_id: this.entity?.id, entity_field_id: this.sections.find( (theme) => theme.key == this.propertyName ).id, }; let url = repoApi.messages.create; this.chatHttpService.postRequest(url, conversation).then((rese) => { //this.getSectionsLastComment(); // this.getEntityQModelInfo(); }); }, getComments(entity_field_id = 1) { let payload = { limit: 100, offset: 0, refrence_id: this.rootCommentId, entity_field_id, }; let url = repoApi.messages.list; this.chatHttpService .postRequest(url, payload) .then((res) => { this.comments = res.data; }) .catch((err) => { this.mySwalToast({ html: err?.message, }); }) .finally(() => {}); }, getSectionsLastComment() { const vm = this; let url = repoApi.messages.sectionLastComment; let payload = { refrence_id: this.rootCommentId, }; this.chatHttpService.postRequest(url, payload).then((res) => { let comments = {}; res.data.forEach((lastCom) => { let themeItem = vm.sections.find( (theme) => theme.id == lastCom.entity_field_id ); if (themeItem) comments[`${vm.rootCommentId}_${themeItem.key}`] = [lastCom]; }); this.comments = comments; this.showComments = true; }); }, updateComment() { const vm = this; let payload = { limit: 50, offset: 0, refrence_id: this.rootCommentId, entity_field_id: this.sections.find( (section) => section.key == this.propertyName ).id, }; let url = repoApi.messages.sectionLastComment; this.chatHttpService.postRequest(url, payload).then((res) => { this.mySwalToast({ html: res.message, }); this.comments = res.data; res.data.forEach((lastCom) => { let themeItem = vm.sections.find( (section) => section.id == lastCom.entity_field_id ); if (themeItem) vm.$set(vm.comments, `${vm.rootCommentId}_${themeItem.key}`, [ lastCom, ]); }); this.counter++; }); }, openNewCommentModal({ event, propertyName, formRefName = "popupMenu", title = undefined, }) { this.propertyName = propertyName; if (title) this.selectedText = title; else this.selectedText = typeof this.entity[propertyName] == "string" ? this.entity[propertyName] : "بدون عنوان"; this.createPopup(event, formRefName); }, closeNewCommentModal() { this.selectedText = ""; this.newComment = false; this.showComments = false; }, hideOtherComments({ propertyName }) { this.propertyName = propertyName; this.showComments = !this.showComments; }, // #endregion }, };