// TODO Remove no-console ignore rule once things are out of rapid dev phase. /* eslint no-console: 0 */ /* global axios, initPagination */ const pageURL = location.pathname window.addEventListener('load', () => { initComments() }) function initComments() { const ce = {} ce.updatePager = initPagination('pagination', onPageChange) window.commentElements = ce ce.commentsCountEl = document.getElementById('comments-count') ce.commentsContainerEl = document.getElementById('comments-container') ce.commentForm = document.getElementById('comment-form') ce.commentReplyForm = document.getElementById('comment-reply-form') ce.commentMessageInput = document.getElementById('comment-message-input') ce.commentSubmitBtn = document.getElementById('comment-submit-btn') ce.commentReplyInput = document.getElementById('comment-reply-input') ce.commentQuoteId = document.getElementById('comment-quote-id') ce.commentSubmitReplyBtn = document.getElementById('comment-submit-reply-btn') ce.replyFormContainer = document.getElementById('reply-form-container') ce.replyCircle = document.getElementById('reply-circle') ce.commentDiv = document.querySelector('.comment-div') ce.commentSubmitBtn && ce.commentSubmitBtn.addEventListener('click', submitComment) // ce.commentMessageInput.addEventListener('keydown', messageInputClick) ce.commentsContainerEl && ce.commentsContainerEl.addEventListener('click', handleCommentClick) ce.commentSubmitReplyBtn && ce.commentSubmitReplyBtn.addEventListener('click', submitCommentReply) ce.commentMessageInput && ce.commentMessageInput.addEventListener('input', autoGrow) ce.commentReplyInput && ce.commentReplyInput.addEventListener('input', autoGrow) // ce.commentMessageInput.addEventListener('keydown', normalizeTextAreaSize) window.commentSetting = {} window.commentSetting.localeOptions = { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' } if (ce.commentMessageInput && ce.commentReplyInput) { ce.commentMessageInput.originalHeight = window.getComputedStyle( ce.commentMessageInput ).height ce.commentReplyInput.originalHeight = window.getComputedStyle( ce.commentReplyInput ).height } if (/\/slovarji\/\d+\/vsebina/.test(pageURL)) { ce.terminsList = document.getElementById('term-list') if (ce.terminsList) { ce.terminsList.addEventListener('click', displayComments) } const ctxBtns = document.getElementsByName('comments-type') if (ctxBtns) { const ctxBtnsArray = Array.from(ctxBtns) ctxBtnsArray.forEach(el => el.addEventListener('click', displayComments)) } } ce.commentMessageInput && ce.commentReplyInput && clearMessageInput() displayComments() } function autoGrow({ target: inputToResize }) { if (!inputToResize.value) { inputToResize.style.height = inputToResize.originalHeight } else { inputToResize.style.height = Math.min(inputToResize.scrollHeight, 95) + 'px' } } function normalizeTextAreaSize() { const { commentMessageInput } = window.commentElements commentMessageInput.style.height = commentMessageInput.originalHeight } // function replyInputClick({ key }) { // if (key === 'Enter') { // const { commentSubmitReplyBtn } = window.commentElements // commentSubmitReplyBtn.click() // } // } async function displayComments(page) { // TODO Add error handler const comments = await fetchComments(page) renderComments(comments) return comments } async function fetchComments(page) { const ctxData = getCtxTypeId() let apiCall = '/api/v1/comments?ctx_type=' + ctxData.ctxType if (ctxData.ctxId !== undefined) apiCall = apiCall + '&' + 'ctx_id=' + ctxData.ctxId if (page) apiCall = apiCall + `&p=${page}` const res = await axios.get(apiCall) return res.data } function renderComments(comments) { const oldComments = document.querySelectorAll('.comment') if (oldComments.length) { const arrayChildren = Array.from(oldComments) arrayChildren.forEach(el => el.remove()) } renderCommentCount(comments.commentCount) comments.comments.forEach(comment => appendComment(comment)) const pagesTotalEl = document.querySelector('.pages-total') // TODO: Think of a better solution for buggy content pagination pagesTotalEl.textContent = comments.numberOfAllPages } function renderCommentCount(commentCount) { const { commentsCountEl } = window.commentElements let displayText = `${commentCount} ` // TODO Let a i18n library handle the following logic. switch (commentCount % 100) { case 1: displayText += 'komentar' break case 2: displayText += 'komentarja' break case 3: case 4: displayText += 'komentarji' break default: displayText += 'komentarjev' break } commentsCountEl.textContent = displayText } function appendComment(comment) { const { commentsContainerEl, commentDiv } = window.commentElements const { localeOptions } = window.commentSetting const { message, author: { firstName: authorFirstName, lastName: authorLastName }, timeCreated, status, quote } = comment let tstamp = new Date(timeCreated).toLocaleTimeString('sl-SL', localeOptions) tstamp = `• ${tstamp}` const divText = document.createElement('div') const divTextComment = document.createElement('div') const divRow = document.createElement('div') const divRowHead = document.createElement('div') const liCommentContainer = document.createElement('li') const divCircle = document.createElement('div') const spanDate = document.createElement('span') const spanName = document.createElement('span') const spanInitials = document.createElement('span') const divCol = document.createElement('div') const aReply = document.createElement('button') const imgReplyBtn = document.createElement('img') const horizontalRowContainer = document.createElement('div') const horizontalRow = document.createElement('hr') divTextComment.className = 'comment-text-2' divRow.className = 'row' divRowHead.className = 'row' divText.className = 'comment-text' liCommentContainer.className = 'comment' liCommentContainer.dataObject = comment divCircle.className = 'comment-initials-container' spanInitials.className = 'test' divCol.className = 'comment-col col' spanName.className = 'comment-author-name' spanDate.className = 'comment-date' aReply.className = 'comment-reply-btn' imgReplyBtn.src = '/images/fi_corner-down-left.svg' imgReplyBtn.alt = 'Reply' horizontalRowContainer.className = 'horizontal-row-container container-l' horizontalRow.className = 'comment-hr comment-line' const firstNameInitial = authorFirstName.slice(0, 1) const lastNameInitial = authorLastName.slice(0, 1) const initialLetters = `${firstNameInitial}${lastNameInitial}` spanInitials.textContent = initialLetters spanDate.textContent = tstamp const authorName = `${authorFirstName} ${authorLastName}` spanName.textContent = authorName divTextComment.textContent = message if (quote) { const { message, author: { firstName: authorFirstName, lastName: authorLastName }, timeCreated } = quote let tstamp = new Date(timeCreated).toLocaleTimeString( 'sl-SL', localeOptions ) tstamp = `• ${tstamp}` const quoteText = document.createElement('div') const imgQuotemarks = document.createElement('img') const spanQuoteName = document.createElement('span') const spanQuoteDate = document.createElement('span') const divQuotedText = document.createElement('div') const quotehr = document.createElement('hr') quoteText.className = 'comment-text-2' imgQuotemarks.src = '/images/quote-marks.svg' spanQuoteName.className = 'comment-quotename' spanQuoteDate.className = 'comment-quotedate' divQuotedText.className = 'comment-quotedtext' quotehr.className = 'comment-quotehr' spanQuoteDate.innerText = tstamp const authorName = `${authorFirstName} ${authorLastName}` spanQuoteName.textContent = authorName divQuotedText.textContent = message divText.appendChild(quoteText) quoteText.appendChild(imgQuotemarks) quoteText.appendChild(spanQuoteName) quoteText.appendChild(spanQuoteDate) quoteText.appendChild(divQuotedText) divQuotedText.appendChild(quotehr) } commentsContainerEl.insertBefore(liCommentContainer, commentDiv) liCommentContainer.appendChild(divRowHead) divRowHead.appendChild(divCircle) divCircle.appendChild(spanInitials) divRowHead.appendChild(divCol) divCol.appendChild(spanName) divCol.appendChild(spanDate) divCol.appendChild(aReply) aReply.appendChild(imgReplyBtn) liCommentContainer.appendChild(divRow) divRow.appendChild(divText) divText.appendChild(divTextComment) divRow.appendChild(horizontalRowContainer) horizontalRowContainer.appendChild(horizontalRow) if (comment.showEye) { const btnSeen = document.createElement('button') const imgSeen = document.createElement('img') divCol.appendChild(btnSeen) btnSeen.appendChild(imgSeen) if (status === 'visible') { btnSeen.className = 'comment-seen-btn me-2' imgSeen.src = '/images/eye.svg' imgSeen.alt = 'Viden' } else { btnSeen.className = 'comment-eye-off-btn me-2' imgSeen.src = '/images/eye-off.svg' imgSeen.alt = 'Skrit' } } if (status === 'hidden') whitenCommentText(liCommentContainer) else normalCommentColors(liCommentContainer) } function submitComment() { const { commentMessageInput } = window.commentElements const message = commentMessageInput.value const ctxData = getCtxTypeId() const ctxType = ctxData.ctxType let ctxId = null if (ctxData.ctxId !== undefined) ctxId = ctxData.ctxId if (!message) { alert('Vaš komentar je brez vsebine.') } else { const payload = { message, ctxType, ctxId, quoteId: null } createComment(payload) } normalizeTextAreaSize() } function submitCommentReply() { const { commentReplyInput, commentQuoteId } = window.commentElements const message = commentReplyInput.value const ctxData = getCtxTypeId() const ctxType = ctxData.ctxType const quoteId = commentQuoteId.value let ctxId = null if (ctxData.ctxId !== undefined) ctxId = ctxData.ctxId if (!message) { alert('Vaš komentar je brez vsebine.') } else { const payload = { message, ctxType, ctxId, quoteId } createComment(payload) } } async function createComment(comment) { const { updatePager } = window.commentElements try { const res = await axios.post('/api/v1/comments', comment) const { comments, pagesTotal } = res.data handleCommentSubmissionSuccess(comments) updatePager(pagesTotal, pagesTotal) } catch (e) { console.log(e) handleCommentSubmissionError() } } function handleCommentSubmissionSuccess(comments) { const { commentForm, replyFormContainer } = window.commentElements commentForm.classList.remove('hide') replyFormContainer.classList.add('hide') clearMessageInput() const commentQuoteContent = document.getElementById('comment-quote-content') if (commentQuoteContent) commentQuoteContent.remove() const oldComments = document.querySelectorAll('.comment') if (oldComments.length) { const arrayChildren = Array.from(oldComments) arrayChildren.forEach(el => el.remove()) } // renderCommentCount(comments.comments.length) comments.forEach(comment => appendComment(comment)) } function clearMessageInput() { const { commentMessageInput, commentReplyInput } = window.commentElements commentMessageInput.value = '' commentReplyInput.value = '' } function handleCommentSubmissionError() { console.error("Comment couldn't be saved. Handle appropriately") } function handleCommentClick({ target }) { const replyButtonEl = target.closest('.comment-reply-btn') const btnSeenEl = target.closest('.comment-seen-btn') const btnEyeOffEl = target.closest('.comment-eye-off-btn') const commentData = target.closest('.comment')?.dataObject let status if (replyButtonEl) { prepareReplyForm(commentData) } else if (btnSeenEl) { const commentText = target.closest('.comment') whitenCommentText(commentText, btnSeenEl) status = 'hidden' commentVisibility(commentData.id, status) } else if (btnEyeOffEl) { const commentText = target.closest('.comment') normalCommentColors(commentText, btnEyeOffEl) status = 'visible' commentVisibility(commentData.id, status) } } function prepareReplyForm(repliedCommentData) { const { id: quoteId, message: quoteMessage, author: { firstName: quoteAuthorFirstName, lastName: quoteAuthorLastName }, timeCreated: quotetimeCreated } = repliedCommentData window.scrollTo(0, document.body.scrollHeight) const { commentForm, commentReplyForm, commentQuoteId, replyFormContainer } = window.commentElements const { localeOptions } = window.commentSetting commentQuoteId.value = quoteId const oldQuotePreview = document.getElementById('comment-quote-content') if (oldQuotePreview) oldQuotePreview.remove() commentForm.classList.add('hide') replyFormContainer.classList.remove('hide') const quotePreview = document.createElement('div') quotePreview.id = 'comment-quote-content' const quoteMarksEl = document.createElement('img') quoteMarksEl.src = '/images/quote-marks.svg' quoteMarksEl.id = 'quote-marks' quotePreview.appendChild(quoteMarksEl) const quoteAuthorFirstNameEl = document.createElement('p') quoteAuthorFirstNameEl.className = 'comment-quote-author-fname' quoteAuthorFirstNameEl.textContent = quoteAuthorFirstName quotePreview.appendChild(quoteAuthorFirstNameEl) const quoteAuthorLastNameEl = document.createElement('p') quoteAuthorLastNameEl.className = 'comment-quote-author-lname' quoteAuthorLastNameEl.textContent = quoteAuthorLastName quotePreview.appendChild(quoteAuthorLastNameEl) let tstamp = new Date(quotetimeCreated).toLocaleTimeString( 'sl-SL', localeOptions ) tstamp = `• ${tstamp}` const quotetimeCreatedEl = document.createElement('p') quotetimeCreatedEl.className = 'comment-quote-time-created' quotetimeCreatedEl.textContent = tstamp quotePreview.appendChild(quotetimeCreatedEl) const shortenQuoteMessage = quoteMessage.substring(0, 175) const quoteMessageEl = document.createElement('p') quoteMessageEl.className = 'comment-quote-message' quoteMessageEl.textContent = shortenQuoteMessage quotePreview.appendChild(quoteMessageEl) const horizontalLineEl = document.createElement('hr') horizontalLineEl.className = 'comment-reply-line' quotePreview.appendChild(horizontalLineEl) // commentReplyForm.insertBefore(quotePreview, commentReplyInput) replyFormContainer.insertBefore(quotePreview, commentReplyForm) const commentReplyInputEl = document.getElementById('comment-reply-input') commentReplyInputEl.focus() commentReplyInputEl.select() } async function commentVisibility(id, status) { try { await axios.post('/api/v1/comments/updateStatus', { params: { id, status } }) } catch (e) { console.log(e) handleCommentSubmissionError() } } function whitenCommentText(selectedText, btnSeenEl) { if (btnSeenEl) { const imgEyeEl = btnSeenEl.lastElementChild imgEyeEl.src = '/images/eye-off.svg' btnSeenEl.classList.remove('comment-seen-btn') btnSeenEl.classList.add('comment-eye-off-btn') } selectedText.classList.add('comment-white-text') } function normalCommentColors(selectedText, btnEyeOffEl) { if (btnEyeOffEl) { const imgEyeEl = btnEyeOffEl.lastElementChild imgEyeEl.src = '/images/eye.svg' btnEyeOffEl.classList.add('comment-seen-btn') btnEyeOffEl.classList.remove('comment-eye-off') } selectedText.classList.remove('comment-white-text') } // function getCtxId({ target }) { // console.log(target) // const termBtn = target.closest('.terms-label') // console.log(termBtn) // if (termBtn) { // console.log(termBtn) // displayComments() // } // } const ctxBtns = document.getElementsByName('comments-type') const commentsTab = document.getElementById('content-comments') if (ctxBtns) { const ctxBtnsArray = Array.from(ctxBtns) ctxBtnsArray.forEach(el => el.addEventListener('click', displayComments)) } if (commentsTab) { commentsTab.addEventListener('click', displayComments) } function getCtxTypeId(type) { const { terminsList } = window.commentElements let ctxType let ctxId const splitURL = pageURL.split('/') if (location.pathname !== '/') { if (!pageURL.match('admin/komentarji')) ctxId = pageURL.match(/\d+/)[0] if (type) ctxId = type } splitURL.forEach(el => { if (el === 'slovarji') { ctxType = 'dictionary' } if (el === 'admin') { ctxType = 'portal' } /* "vebina" -> in case this endpoint is used anywhere in the future, delete this legacy endpint matching otherwise */ if (el === 'vsebina') { const selected = checkButton('intext') if (selected === 'internal') ctxType = 'entry_dict_int' if (selected === 'external') ctxType = 'entry_dict_ext' const selectedTerm = checkButton(terminsList) ctxId = selectedTerm } if (el === 'termin') { ctxType = 'entry_dict_ext' ctxId = checkButtonModular('returnFromEntryDetail') } if (el === 'urejanje') { ctxType = 'entry_consult_int' ctxId = checkButtonModular('returnFromCosnultancyInternal') } }) if (location.pathname === '/') { ctxType = 'portal' ctxId = null } return { ctxType: ctxType, ctxId: ctxId } } function checkButton(type) { let checked if (type === 'intext') { checked = document.querySelector('input[name="comments-type"]:checked').id } else { const term = document.querySelector('.selected-term-btn') if (term) { const termId = term.getAttribute('data-term-id') checked = termId } } return checked } async function onPageChange(newPage) { const { updatePager } = window.commentElements try { const { page, numberOfAllPages } = await displayComments(newPage) updatePager(page, numberOfAllPages) } catch (error) { let message = 'Prišlo je do napake.' if (error.response?.data) { message = error.response.data } else if (error.request) { message = 'Strežnik ni dosegljiv. Poskusite kasneje.' } alert(message) updatePager() } } /** * improved version of checkButton function * * @param {*} type * Parameter that checks whether the selector should choose: * -> if 'intext' -> returns internal or external comment type * -> if namePattern = returnFrom[Dictionary | Entry | OtherName]Detail * | -> returns context ID from URL depending on its location * -> else -> returns the context ID of the selected TERM (also known as entry) * (previous fn compatible) * @param {*} selectorExternal * Parameter that defines from which element to read the ID in case type * isn't "intext". Please notice that the attribute for id is not id, but * data-term-id * @param {*} selectorInternal * Parameter that defines where to read the string "internal" or "external" * in case of type being "intext" * @returns * * #Note to the author of the fn: Please define function such that * one function only does one job, not nested into different logic * based on a string. */ function checkButtonModular( type, selectorExternal = '.selected-term-btn', selectorInternal = 'input[name="comments-type"]:checked' ) { let checked if (type === 'intext') { checked = document.querySelector(selectorInternal).id } else if (type === 'returnFromEntryDetail') { checked = returnCommentCTXFromParsedURL(type) } else if (type === 'returnFromCosnultancyInternal') { checked = returnCommentCTXFromParsedURL(type) } else { const term = document.querySelector(selectorExternal) if (term) { const termId = term.getAttribute('data-term-id') checked = termId } } return checked } function returnCommentCTXFromParsedURL(typeOfURL) { let retString = 'Undefined - please check your code' if (typeOfURL === 'returnFromEntryDetail') { const splitStringBat = window.location.href.split('/') retString = splitStringBat[splitStringBat.length - 1] } else if (typeOfURL === 'returnFromDictionaryDetail') { const splitStringBat = window.location.href.split('/') retString = splitStringBat[splitStringBat.length - 2] } else if (typeOfURL === 'returnFromCosnultancyInternal') { const splitStringBat = window.location.href.split('/') retString = splitStringBat[splitStringBat.length - 1].split('?')[0] } // console.log('ID: ' + retString) return retString }