Initial commit

This commit is contained in:
Luka Romih
2022-12-07 04:43:36 +01:00
commit 257f3c354f
496 changed files with 55373 additions and 0 deletions
+222
View File
@@ -0,0 +1,222 @@
/* global axios, bootstrap */
const mainContainer = document.getElementById('offset-main')
mainContainer.addEventListener('click', clickHandler)
function clickHandler({ target }) {
const createPortalBtn = target.closest('.create-portal-btn')
const linkPortalBtn = target.closest('.link-portal-btn')
const syncPortalBtn = target.closest('.sync-portal-btn')
const portalEnableSwitch = target.closest('.portal-enable-switch')
const deleteTaskBtn = target.closest('.delete-task')
if (createPortalBtn) {
const indexURLEl = document.getElementById('index-url')
const indexURL = indexURLEl.value
createPortal(indexURL)
}
if (linkPortalBtn) {
const linkedPortalId = linkPortalBtn.getAttribute('data-link-id')
const closestTask = linkPortalBtn.closest('.task-declined')
linkPortal(linkedPortalId, closestTask)
}
if (syncPortalBtn) {
const linkedPortalId = syncPortalBtn.getAttribute('data-link-id')
syncPortal(linkedPortalId)
}
if (portalEnableSwitch) {
const portalId = portalEnableSwitch.getAttribute('data-portal-id')
const isEnabled =
portalEnableSwitch.querySelector('.form-check-input').checked
updatePortalStatus(portalId, isEnabled)
}
if (deleteTaskBtn) {
const alertModal = new bootstrap.Modal(
document.getElementById('alert-modal')
)
alertModal.toggle()
const taskId = deleteTaskBtn.getAttribute('data-link-id')
const modalUseBtn = document.getElementById('modal-use-btn')
modalUseBtn.addEventListener('click', deleteConnection(taskId))
}
}
async function createPortal(indexURL) {
event.preventDefault()
const form = document.getElementById('form-connection-new')
const payload = new URLSearchParams(new FormData(form))
try {
const id = await axios.post('/api/v1/portals/createPortal', payload)
await linkPortal(id.data[0].id)
} catch (error) {
console.log(error)
} finally {
window.location.href = '../../../admin/povezave/seznam'
}
}
async function deleteConnection(id) {
try {
await axios.delete(`/api/v1/portals/${id}/deleteLinkedDictionary`)
alertModal.hide()
} catch (error) {
console.log(error)
}
}
async function linkPortal(linkedPortalId, type) {
try {
const r = await axios.put(`/api/v1/portals/${linkedPortalId}/dictionaries`)
// if (data.length) {
// await insertPortalDictionaries(data, linkedPortalId)
// if (type) {
// changeContent(data, type)
// }
// }
console.log(r)
} catch (error) {
console.log(error)
}
}
async function insertPortalDictionaries(data, portalId) {
data.forEach(el => (el.linkedPortalId = portalId))
try {
// Ta enpoint je bil odstranjen, ker je itak nepotreben. Tudi ta funkcija je nepotrebna.
// TODO Odstrani to funkcijo in prilagodi lokacije, kjer je sedaj klicana.
// await axios.post('/api/v1/portals/insertDictionaries', {
// params: { data }
// })
} catch (error) {
console.log(error)
}
}
async function syncPortal(linkedPortalId) {
event.preventDefault()
const responseModal = new bootstrap.Modal(
document.getElementById('response-modal')
)
const responseModalText = document.getElementById('response-modal-text')
try {
axios
.all([
axios.put(`/api/v1/portals/${linkedPortalId}/dictionaries`),
axios.get('/api/v1/portals/getMyDictionaries', {
params: { id: linkedPortalId }
})
])
.then(
axios.spread((obj1, obj2) => {
const theirDict = obj1.data
const myDict = obj2.data
const results = theirDict.filter(
({ id: id1 }) =>
!myDict.some(({ target_dictionary_id: id2 }) => id2 === id1)
)
responseModalText.textContent = `Spremenjenih je bilo ${results.length} slovarjev.`
if (results.length) {
insertPortalDictionaries(results, linkedPortalId)
responseModal.toggle()
} else {
responseModal.toggle()
}
})
)
} catch (error) {
console.log(error)
}
}
async function updatePortalStatus(portalId, isEnabled) {
try {
await axios.post('/api/v1/portals/updatePortalStatus', {
params: { id: portalId, isEnabled: isEnabled }
})
} catch (error) {
console.log(error)
}
}
function changeContent(data, type) {
const nameEl = document.querySelector('.bold-weight-black')
const name = nameEl.textContent
const indexURLEl = document.querySelector('.link-url')
const indexURL = indexURLEl.textContent
const imageLinkEl = document.querySelector('.link-portal-btn')
const id = imageLinkEl.getAttribute('data-link-id')
const syncedDiv = document.getElementById('synced-terms-div')
const contaner2 = document.createElement('div')
contaner2.className = 'd-sm-flex justify-content-sm-between d-grid'
const gridEl = document.createElement('div')
gridEl.className = 'd-grid'
const spanCode = document.createElement('span')
spanCode.className = 'bold-weight-black'
spanCode.textContent = name
const spanURL = document.createElement('span')
spanURL.className = 'normal-gray mt-2.mb-2'
spanURL.textContent = indexURL
const centerDiv = document.createElement('div')
centerDiv.className = 'd-flex align-items-center justify-content-sm-end'
const btnDict = document.createElement('button')
btnDict.className = 'btn btn-secondary align-items-center d-flex'
const imgDict = document.createElement('img')
const spanDict = document.createElement('span')
spanDict.className = 'ms-1'
const hr = document.createElement('hr')
hr.className = 'mt-2 mb-3'
const betweenEl = document.createElement('div')
betweenEl.className = 'd-sm-flex justify-content-between'
const alignCntEl = document.createElement('div')
const alertDiv = document.createElement('div')
alertDiv.className = 'd-flex align-content-center mb-0 me-3 mt-2 mt-sm-0'
const alertmargin = document.createElement('div')
alertmargin.className = 'ms-3'
const deleteBtn = document.createElement('button')
deleteBtn.className = 'p-0 btn delete-task'
const deleteImg = document.createElement('img')
deleteImg.src = '/images/red-trash-icon.svg'
const spanDelete = document.createElement('span')
spanDelete.className = 'normal-gray ms-2'
spanDelete.textContent = 'Odstrani'
if (data.length) {
const container = document.createElement('div')
container.className = 'container-fluid task task-completed p-3 mb-4'
const aDictEl = document.createElement('a')
aDictEl.className = 'btn btn-secondary align-items-center d-flex'
aDictEl.href = `/admin/povezave/seznam/${id}`
imgDict.src = '/images/book-colorized.svg'
spanDict.textContent = 'Slovarji'
alignCntEl.className =
'd-sm-flex align-items-center mb-0 form-check form-switch'
const switchEl = document.createElement('input')
switchEl.className = 'form-check-input'
switchEl.type = 'checkbox'
switchEl.name = 'isEnabled'
switchEl.id = 'checkbox' + id
const spanSwitch = document.createElement('span')
spanSwitch.className = 'normal-gray ms-1 mt-1'
spanSwitch.textContent = 'Omogočeno'
container.appendChild(contaner2)
contaner2.appendChild(gridEl)
gridEl.appendChild(spanCode)
contaner2.appendChild(centerDiv)
centerDiv.appendChild(aDictEl)
aDictEl.appendChild(imgDict)
aDictEl.appendChild(spanDict)
container.appendChild(hr)
container.appendChild(betweenEl)
betweenEl.appendChild(alignCntEl)
alignCntEl.appendChild(switchEl)
alignCntEl.appendChild(spanSwitch)
betweenEl.appendChild(alertDiv)
alertDiv.appendChild(alertmargin)
alertmargin.appendChild(deleteBtn)
deleteBtn.appendChild(deleteImg)
deleteBtn.appendChild(spanDelete)
syncedDiv.appendChild(container)
type.remove()
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
{
let isVisible = false
const pager = document.querySelector('.pager')
const commentsContainer = document.querySelector('#comments-container')
const dropImage = document.querySelector('#dropImage')
const commentCount = document.querySelector('#comments-count')
const hide = () => {
pager.className += ' d-none'
commentsContainer.className += ' d-none'
commentCount.className += ' d-none'
}
document.getElementById('collapseComments').addEventListener('click', e => {
isVisible = !isVisible
if (isVisible) {
pager.className = 'pager'
commentsContainer.className = 'comments-container pe-1'
dropImage.src = '/images/arrow_drop_up.svg'
commentCount.className = 'comment-count d-flex ms-auto me-4 text-p875rem'
} else {
dropImage.src = '/images/arrow_drop_down.svg'
hide()
}
})
hide()
}
+651
View File
@@ -0,0 +1,651 @@
// 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
}
@@ -0,0 +1,71 @@
/* global $, axios */
function sumbitFormForConsultancy(e) {
const id = document.querySelector('#entry-id').value
const questionTitle = document.querySelector('#question-title').value
const domain = document.querySelector('#select-cerif').value
const question = document.querySelector('#question').value
const answer = $('#opinion').summernote('code')
axios.put(e.target.action, {
id,
questionTitle,
domain,
question,
answer
})
axios
.put('/api/v1/consultancy/entry', {
id,
questionTitle,
domain,
question,
answer
})
.then(() => {
window.location.href = '/svetovanje/vprasanje/admin/v-delu'
})
}
{
/*
$('#opinion').summernote({
codeviewFilter: true,
codeviewIframeFilter: true
})
*/
const ef = document.querySelector('#edit-form')
ef.addEventListener('submit', e => {
e.preventDefault()
sumbitFormForConsultancy(e)
})
ef.addEventListener('change', e => {
document.querySelector('#opt1').disabled = false
})
$('#opinion').on('summernote.change', function (we, contents, $editable) {
document.querySelector('#opt1').disabled = false
})
const resizeFields = document.querySelectorAll('.explanation-field')
if (resizeFields.length) {
resizeFields.forEach(el =>
el.addEventListener('input', () => autoResize(el))
)
}
function autoResize(el) {
if (el.style) {
el.style.height = 42 + 'px'
el.style.height = `${el.scrollHeight}px`
}
}
resizeFields.forEach(el => {
autoResize(el)
})
}
@@ -0,0 +1,6 @@
/* global $ , unsavedData, sumbitFormForConsultancy */
const form = document.getElementById('edit-form')
const sideMenu = document.querySelector('.admin-nav-content')
unsavedData(form, sideMenu, sumbitFormForConsultancy)
@@ -0,0 +1,67 @@
/* global $, inputMainSearch */
const searchButton = document.getElementById('couns-search-btn')
const advancedSearchButton = document.getElementById('search-in-filter-modal')
function routeConsultancy(searchString) {
let url
if (searchString) {
url = new URL(searchString, location.protocol + '//' + location.host)
} else {
url = new URL(location)
const sq = document.getElementById('search-query')
if (sq) {
url.searchParams.set('q', sq.value)
}
}
// const qParams = new URL(location).searchParams
// error prone code because of clear filter
// qParams.forEach((value, key) => {
// if (key !== 'q') {
// url.searchParams.append(key, value)
// }
// })
const pds = $('.select-domain-field')
if (pds) {
pds.val().forEach(val => {
url.searchParams.append('pd', val)
})
}
window.location.href = url
}
function searchOnConsultancyBaseOrAdmin() {
const isItNotAdminURL = !location.pathname.includes('/admin/')
if (isItNotAdminURL) {
const sq = document.getElementById('search-query')
routeConsultancy(`/svetovanje/iskanje?q=${sq ? sq.value : ''}`)
} else {
// build here URL for admin sections
routeConsultancy(null)
}
}
function propagateFunctionalityToASearchButton(el) {
if (el) {
el.addEventListener('click', searchOnConsultancyBaseOrAdmin)
}
}
if (inputMainSearch) {
const listenForTypingOnConsultancy = function (event) {
if (event.code === 'Enter') {
event.preventDefault()
searchOnConsultancyBaseOrAdmin() // here you invoke the function since it is not a listener
}
}
inputMainSearch.addEventListener('keyup', listenForTypingOnConsultancy)
}
propagateFunctionalityToASearchButton(searchButton)
propagateFunctionalityToASearchButton(advancedSearchButton)
+440
View File
@@ -0,0 +1,440 @@
/* global $, handleDomainsClickForConsultancy, inputFieldsChecker, currentPagePath, axios, nthParent, tooltipListWOL, tooltipTriggerList */
/*
author: Miha Stele, 2022
*/
/** selectedID -> ID of the selected Entry */
let selectedID = -1
// consultancy FORM
let offsetMain
function adjustOffsetBy() {
offsetMain = document.querySelector('#offset-main')
const fixedTopSection = document.querySelector('#fixed-top-section')
const referenceHeight = fixedTopSection.offsetHeight
const offsetHeader = document.getElementsByClassName('offset-header')
const offsetHeaderPadding = document.getElementById('offset-padding')
// const adminNavMobile = document.getElementsByClassName('admin-nav')
const headerPadding = document.getElementById('header-padding')
if (document.body.clientWidth < 1200) {
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
} else {
for (let i = 0; i < offsetHeader.length; i++) {
offsetHeader[i].style.paddingTop = `${referenceHeight}px`
}
if (headerPadding !== null) {
const headerPaddingHeight = headerPadding.offsetHeight
offsetMain.style.paddingTop = `${headerPaddingHeight}px`
}
if (offsetHeaderPadding !== null) {
const offsetHeaderHeight = offsetHeaderPadding.offsetHeight
offsetMain.style.paddingTop = `${referenceHeight + offsetHeaderHeight}px`
}
}
}
function event2SelectedId(e) {
return nthParent(e.currentTarget, 4).id.substring(3)
}
window.addEventListener('load', () => {
adjustOffsetBy()
const ce = {}
window.adminElements = ce
/* copy source admin.js in case of refactoring */
if (currentPagePath === '/svetovanje/vprasanje/admin/uporabniki') {
offsetMain.addEventListener('click', handleDomainsClickForConsultancy)
ce.name = document.getElementById('name-input')
if (ce.name) {
ce.name.addEventListener('input', inputFieldsChecker)
}
ce.inputAreaEl = document.getElementById('area-input')
if (ce.inputAreaEl) {
ce.inputAreaEl.addEventListener('input', inputFieldsChecker)
}
}
})
window.addEventListener('resize', () => {
adjustOffsetBy()
// mobileMoveContent()
})
try {
document.querySelector('#consultancy-form').addEventListener('submit', e => {
e.preventDefault()
// window.location.replace('/svetovanje/iskanje')
})
} catch (e) {}
try {
const splitedHref = window.location.href.split('/')
document.querySelectorAll('.edit-button').forEach(e => {
e.addEventListener('click', () => {
window.location.replace(
`/svetovanje/vprasanje/admin/urejanje/${nthParent(e, 4).id.slice(
3
)}?sentFrom=${splitedHref[splitedHref.length - 1]}`
)
})
})
} catch (e) {}
// share.pug
try {
// In your Javascript (external .js resource or <script> tag)
$(document).ready(function () {
$('.share-search-consultants').select2({
dropdownParent: $('#consultancyModalShare')
})
})
} catch (e) {}
// assign.pug
try {
// In your Javascript (external .js resource or <script> tag)
$(document).ready(function () {
$('.assign-search-consultants').select2({
dropdownParent: $('#consultancyModalAssign')
})
})
} catch (e) {}
const summernote = $('.summernote')
if (summernote) {
summernote.summernote({
placeholder: 'Na kratko opišite zasnovo in namen slovarja.',
height: 300,
minheight: 150,
toolbar: [
['style', ['style', 'bold', 'italic', 'underline']],
['font', ['superscript', 'subscript']],
['link', ['linkDialogShow']],
['para', ['ul', 'ol']],
['table', ['table']],
['insert', ['picture']]
],
styleTags: ['p', 'h3', 'h4']
})
}
/*
mixin shareResultsItem(userId, name)
.share-results-item(id=userId)
.row
.col-11
p.mb-0.navigation-text-color.mb0 #{ name }
.col-1
.d-flex.justify-content-end
button.delete-shared-cons.bg-transparent.no-border
img(src="/images/trash-2.svg")
hr.mt-1.mb-1
*/
function buildSharedAuthorListItem(id, name) {
const root = document.createElement('div')
root.className = 'share-results-item'
root.id = id
const row = document.createElement('div')
row.className = 'row'
root.appendChild(row)
const hr = document.createElement('hr')
hr.className = 'mt-1 mb-1'
root.appendChild(hr)
const col11 = document.createElement('div')
col11.className = 'col-11'
row.appendChild(col11)
const col1 = document.createElement('div')
col1.className = 'col-1'
row.appendChild(col1)
const nameEl = document.createElement('p')
nameEl.className = 'mb-0 navigation-text-color mb0'
nameEl.textContent = name
col11.appendChild(nameEl)
const aligner = document.createElement('div')
aligner.className = 'd-flex justify-content-end'
col1.appendChild(aligner)
const btn = document.createElement('btn')
btn.className = 'delete-shared-cons bg-transparent no-border'
aligner.appendChild(btn)
const img = document.createElement('img')
img.src = '/images/trash-2.svg'
btn.appendChild(img)
btn.addEventListener('click', e => {
const element = nthParent(e.currentTarget, 4)
axios
.delete('/api/v1/consultancy/delete-consultant-author', {
data: {
question_id: selectedID,
user_id: element.id
}
})
.then(() => {
element.remove()
})
})
root.appendChild(row)
return root
}
const assignModalBtns = document.querySelectorAll('.consultancy-modal-assign')
// assign button logic NOTE you can only have assignBtn when you have
// assign buttons (one or more) that trigger the assign modal (assignModalBtns)
// otherwise they're useless
if (assignModalBtns.length > 0) {
// let modalQuestionBatteryId = -1
assignModalBtns.forEach(assignButton => {
assignButton.addEventListener('click', e => {
$('#consultancyModalAssign').modal('toggle')
selectedID = parseInt(event2SelectedId(e))
})
})
const assignBtn = document.querySelector('#assign-btn')
if (assignBtn) {
assignBtn.addEventListener('click', e => {
// TODO, assign the question to the in progress section if you are consultancy admin or portal admin
const userId = document.querySelector('#assign-sel').value
if (userId !== 'NONE_SELECTED_PLACEHOLDER') {
axios
.put('/api/v1/consultancy/assign', {
question_id: selectedID,
user_id: document.querySelector('#assign-sel').value
})
.then(() => {
window.location.href += ''
})
} else {
console.log('Please select different username')
}
})
}
}
const shareButtons = document.querySelectorAll('.consultancy-modal-share')
if (shareButtons.length > 0) {
document.querySelector('#add-shared-author').addEventListener('click', () => {
axios
.post('/api/v1/consultancy/add-non-moderator', {
question_id: selectedID,
user_id: document.querySelector('#share-sel').value
})
.then(e => {
const sel = document.querySelector('#share-sel')
document
.querySelector('.share-results-container')
.appendChild(
buildSharedAuthorListItem(
selectedID,
sel.options[sel.selectedIndex].text
)
)
})
})
document.querySelector('#close-shared').addEventListener('click', () => {
window.location.href += ''
})
shareButtons.forEach(assignButton => {
assignButton.addEventListener('click', e => {
selectedID = parseInt(event2SelectedId(e))
axios
.get(`/api/v1/consultancy/get-shared-authors?id=${selectedID}`)
.then(res => {
const rootCont = document.querySelector('.share-results-container')
rootCont.innerHTML = ''
const authors = res.data
authors.forEach(author => {
rootCont.appendChild(
buildSharedAuthorListItem(
author.user_id,
`${author.first_name} ${author.last_name} (${author.username})`
)
)
})
})
$('#consultancyModalShare').modal('toggle')
})
})
}
const rejectButtons = document.querySelectorAll('.reject-item')
if (rejectButtons.length > 0) {
rejectButtons.forEach(rejectButton => {
rejectButton.addEventListener('click', e => {
selectedID = parseInt(event2SelectedId(e))
// $('#alert-modal').modal('toggle')
axios
.put('/api/v1/consultancy/reject', {
question_id: selectedID
})
.then(() => {
window.location.href += ''
})
})
})
}
const deleteButtons = document.querySelectorAll('.delete-item')
if (deleteButtons.length > 0) {
document.querySelector('#del-btn').addEventListener('click', e => {
axios
.delete('/api/v1/consultancy/delete', {
data: { id: selectedID }
})
.then(() => {
window.location.href += ''
})
})
deleteButtons.forEach(assignButton => {
assignButton.addEventListener('click', e => {
selectedID = parseInt(event2SelectedId(e))
$('#deleteModal').modal('toggle')
})
})
}
const reviewBtns = document.querySelectorAll('.review-btn')
if (reviewBtns.length > 0) {
reviewBtns.forEach(reviewBtn => {
reviewBtn.addEventListener('click', e => {
selectedID = parseInt(event2SelectedId(e))
axios
.put('/api/v1/consultancy/review', {
question_id: selectedID
})
.then(() => {
window.location.href += ''
})
})
})
}
const publishBtns = document.querySelectorAll('.publish-btn')
if (publishBtns.length > 0) {
publishBtns.forEach(publishBtn => {
publishBtn.addEventListener('click', e => {
selectedID = parseInt(event2SelectedId(e))
const answerAuthorsWithComma =
document.getElementById('moderatorName').innerText +
',' +
(nthParent(
e.currentTarget,
3
).childNodes[0].childNodes[0].childNodes[4].getAttribute(
'data-tooltip-content'
) ?? ',')
axios
.put('/api/v1/consultancy/publish', {
question_id: selectedID,
answer_authors: answerAuthorsWithComma.slice(0, -1)
})
.then(() => {
window.location.href += ''
})
})
})
}
// Share authors boilerplate code
/// // SUBMIT FUNCTIONS
const createAnswerForm = document.querySelector('#create-consultancy-question')
if (createAnswerForm) {
async function onCreateAnswerSubmit(event) {
event.preventDefault()
if (createAnswerForm && !createAnswerForm.checkValidity()) {
event.stopPropagation()
createAnswerForm.classList.add('was-validated')
return
}
const payload = Object.fromEntries(new FormData(event.target))
const res = await axios.post(event.target.action, payload)
// const data = res.data
// console.log(data)
if (res.status === 201) {
window.location.href = '/svetovanje'
}
}
createAnswerForm.addEventListener('submit', onCreateAnswerSubmit)
}
const insertConsultantForm = document.querySelector('#insert-consultant')
if (insertConsultantForm) {
insertConsultantForm.addEventListener('submit', e => {
e.preventDefault()
const url = e.currentTarget.action
const username = document.querySelector('#username').value
const domains = document.querySelector('#domains').value
axios
.post(url, {
username: username,
domains: domains
})
.then(result => {
console.log(result)
window.location.href = '/svetovanje/vprasanje/admin/uporabniki'
})
})
}
try {
if (tooltipTriggerList.length > 0) {
// initialize tooltips
tooltipListWOL('author-list-tooltip') // ADD for tooltip debug in the end -> [0].show()
}
} catch (e) {
console.log(e)
}
/*
if (tooltipList) {
console.log(tooltipList)
tooltipList.forEach(ptl => {
console.log(ptl)
})
}
*/
// focus required field section
$(document).ready(() => {
const focused = $('#description')
if (focused) {
focused.focus()
}
})
@@ -0,0 +1,172 @@
/* global axios */
// Priporočam uporabo block scopa okoli paginacijske logike za čimvečjo izolacijo.
{
// Referenca na element, kamor se izrisuje seznam rezultatov.
const resultsListEl = document.getElementById('page-results')
// Paginacijo inicializiraš s klicem funkcije initPagination:
// 1. parameter: id pager elementa. V demo-paginacija.pug je to #pagination.
// 2. parameter: callback funkcijo, ki jo pager pokliče vsakič, ko uporabnik zahteva novo stran. Poliče jo s številko zahtevane strani.
// vrnjena vrednost: funkcija, ki jo (kasneje) kličeš za posodobitev pagerja. Tu jo poimenujem updateDemoPager.
const updateDemoPager = initPagination('pagination', onPageChange)
// Fukncija, prejme številko nove strani in naj:
// 1. Pridobi podatke nove strani.
// 2. Izriše seznam elementov te strani.
// 3. Posodobi pager, tako, da kliče funkcijo, ki jo je vrnil klic initPagination (updateDemoPager) z novo stranjo in številom vseh strani.
async function onPageChange(newPage) {
try {
const { page, numberOfAllPages, results } = await getDataForPage(newPage)
removeAllChildNodes(resultsListEl)
renderResults(results)
updateDemoPager(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)
updateDemoPager()
}
}
// Primer helper funkcije za pridobitev podatkov želene strani.
async function getDataForPage(page) {
const url = `/api/v1/demo-paginacija/list?p=${page}`
const { data } = await axios.get(url)
return data
}
// Primer helper funkcije za izris seznama novih podatkov.
function renderResults(results) {
results.forEach(result => {
const newListEl = document.createElement('li')
const textNode1 = document.createTextNode('Zanimiva vrednost: ')
const boldedEl = document.createElement('b')
boldedEl.textContent = result.zanimivo
const textNode2 = document.createTextNode(
`. Totalno nezanimivo: ${result.nezanimivo1} in ${result.nezanimivo2}`
)
newListEl.append(textNode1, boldedEl, textNode2)
resultsListEl.appendChild(newListEl)
})
}
}
/** ****************************************************************************************************************************************** **\
* Koda od tu navzdol za vaju ni relevantna. Tu je, da dela zgornja koda. Za realno uporabo sem je skopiral tudi v public/javascripts/scripts.js *
* Na vsaki strani, kjer bo paginacija, jo inicializiraj in uporabljaj po zgledu zgornje kode. *
\** ****************************************************************************************************************************************** **/
function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
const rootEl = document.getElementById(paginationRootElId)
const btnFirstPage = rootEl.querySelector('.first-page')
const btnPreviousPage = rootEl.querySelector('.previous-page')
const btnNextPage = rootEl.querySelector('.next-page')
const btnLastPage = rootEl.querySelector('.last-page')
const formEl = rootEl.querySelector('form')
const pageInputEl = formEl.querySelector('input')
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
let reqLock = false
rootEl.addEventListener('click', handleButtonClick)
formEl.addEventListener('submit', handleFormSubmit)
function handleButtonClick({ target }) {
if (reqLock) return
const buttonEl = target.closest(`#${paginationRootElId} button`)
if (!buttonEl) return
const numOfAllPages = +pagesCountDisplayEl.textContent
if (buttonEl.classList.contains('first-page')) {
if (currentPage === 1) return
enableLock()
onPageChange(1)
} else if (buttonEl.classList.contains('previous-page')) {
if (currentPage === 1) return
enableLock()
onPageChange(currentPage - 1)
} else if (buttonEl.classList.contains('next-page')) {
if (currentPage === numOfAllPages) return
enableLock()
onPageChange(currentPage + 1)
} else if (buttonEl.classList.contains('last-page')) {
if (currentPage === numOfAllPages) return
enableLock()
onPageChange(numOfAllPages)
}
}
function handleFormSubmit(e) {
e.preventDefault()
if (reqLock) return
const inputValue = +pageInputEl.value
if (!(inputValue > 0 && inputValue <= pagesCountDisplayEl.textContent)) {
alert('Nepravilna vrednost strani')
pageInputEl.value = currentPage
return
}
enableLock()
onPageChange(inputValue)
}
function enableLock() {
reqLock = true
btnFirstPage.disabled = true
btnPreviousPage.disabled = true
btnNextPage.disabled = true
btnLastPage.disabled = true
pageInputEl.disabled = true
}
function disableLock() {
reqLock = false
btnFirstPage.disabled = false
btnPreviousPage.disabled = false
btnNextPage.disabled = false
btnLastPage.disabled = false
pageInputEl.disabled = false
}
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
disableLock()
if (!newCurrentPage) return
currentPage = newCurrentPage
pageInputEl.value = newCurrentPage
pagesCountDisplayEl.textContent = newNumOfAllPages
if (newCurrentPage === 1) {
btnFirstPage.disabled = true
btnPreviousPage.disabled = true
} else {
btnFirstPage.disabled = false
btnPreviousPage.disabled = false
}
if (newCurrentPage === newNumOfAllPages) {
btnNextPage.disabled = true
btnLastPage.disabled = true
} else {
btnNextPage.disabled = false
btnLastPage.disabled = false
}
}
return updatePagerUi
}
// Helper function to easily remove all child nodes. Useful for pagination.
function removeAllChildNodes(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,72 @@
/* global axios, initPagination, removeAllChildNodes */
{
const selectorEl = document.getElementById('select-extraction-name')
selectorEl.addEventListener('change', () => loadCandidates(selectorEl.value))
const resultsListEl = document.getElementById('page-results')
let termCandidates
let hitsPerPage
let numberOfAllPages
async function loadCandidates(id) {
try {
const { data } = await axios.get(
`/api/v1/extraction/${id}/term-candidates`
)
termCandidates = data.termCandidates
hitsPerPage = data.hitsPerPage
numberOfAllPages = data.numberOfAllPages
removeAllChildNodes(resultsListEl)
const results = getDataForFirstPage(data)
renderResults(results)
updateDemoPager(1, numberOfAllPages)
} catch (error) {
console.log(error)
}
}
const updateDemoPager = initPagination('pagination', onPageChange)
function onPageChange(newPage) {
const results = getDataForPage(newPage)
removeAllChildNodes(resultsListEl)
renderResults(results)
updateDemoPager(newPage, numberOfAllPages)
}
function getDataForPage(page) {
const sliceStart = (page - 1) * hitsPerPage
const sliceEnd = page * hitsPerPage
const onePageOfTermCandidates = termCandidates.slice(sliceStart, sliceEnd)
const data = onePageOfTermCandidates.map((candidate, index) => {
const sequentialCount = sliceStart + index + 1
return [sequentialCount, candidate]
})
return data
}
function getDataForFirstPage() {
const sliceStart = (1 - 1) * hitsPerPage
const sliceEnd = 1 * hitsPerPage
const onePageOfTermCandidates = termCandidates.slice(sliceStart, sliceEnd)
const data = onePageOfTermCandidates.map((candidate, index) => {
const sequentialCount = sliceStart + index + 1
return [sequentialCount, candidate]
})
return data
}
function renderResults(results) {
results.forEach(([sequentialCount, candidate]) => {
const rowEl = document.createElement('tr')
const tdId = document.createElement('td')
const tdName = document.createElement('td')
const tdSize = document.createElement('td')
const tdDate = document.createElement('td')
tdId.textContent = sequentialCount
tdName.textContent = candidate.kanonicnaoblika
tdSize.textContent = candidate.ranking
tdDate.textContent = candidate.pogostostpojavljanja
rowEl.append(tdId, tdName, tdSize, tdDate)
resultsListEl.appendChild(rowEl)
})
}
}
@@ -0,0 +1,163 @@
/* global axios, removeAllChildNodes, bootstrap */
const fileUploadForm = document.forms['upload-files']
const fileInputEl = fileUploadForm.querySelector('input[type="file"]')
const filesListEl = document.getElementById('files-list')
const dropArea = document.querySelector('.drag-area')
const dragButton = dropArea.querySelector('button')
const dragInput = dropArea.querySelector('input')
const modalSpinner = new bootstrap.Modal(
document.getElementById('modal-spinner')
)
const modalAlert = new bootstrap.Modal(document.getElementById('alert-modal'))
let file
dragButton.onclick = () => {
dragInput.click()
}
dragInput.addEventListener('change', function () {
file = this.files[0]
dropArea.classList.add('active')
// showFile(file)
})
dropArea.addEventListener('dragover', event => {
event.preventDefault()
})
dropArea.addEventListener('dragleave', () => {})
dropArea.addEventListener('drop', event => {
event.preventDefault()
fileInputEl.files = event.dataTransfer.files
submitFiles()
})
// offsetMain.addEventListener('click', handleClick)
// function handleClick({ target }) {
// const row = target.closest('.delete-btn-table')
// if (row) {
// deleteRow(row)
// }
// function deleteRow(ele) {
// // console.log(ele)
// const element = ele.closest('tr')
// element.remove()
// }
// }
const extractionId = +fileUploadForm.extractionId.value
let apiEndpointBase
switch (location.pathname.split('/').at(-1)) {
case 'besedila':
apiEndpointBase = `/api/v1/extraction/${extractionId}/documents`
break
case 'stop-termini':
apiEndpointBase = `/api/v1/extraction/${extractionId}/stop-terms`
break
default:
throw Error("apiEndpointBase couldn't be determined")
}
fileInputEl.addEventListener('change', submitFiles)
filesListEl.addEventListener('click', handleFileClick)
async function submitFiles() {
// TODO Lock additional submits for the duration of this function execution?
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
const failedUploads = []
modalSpinner.toggle()
for (const file of fileInputEl.files) {
if (file.size > MAX_FILE_SIZE) {
const failedUpload = {
filename: file.name,
message: 'File too large. Must not be over 1 GB.'
}
failedUploads.push(failedUpload)
continue
}
const payload = new FormData()
payload.set(fileInputEl.name, file)
try {
await axios.put(apiEndpointBase, payload)
} catch (error) {
const failedUpload = {
filename: file.name,
message: error.response.data
}
failedUploads.push(failedUpload)
}
}
try {
const { data: files } = await axios.get(apiEndpointBase)
updateFilesList(files)
} catch {
alert('Pri posodobljanju seznama naloženih datotek je prišlo do napake.')
}
fileInputEl.value = ''
displayFailedUploads(failedUploads)
modalSpinner.toggle()
}
function updateFilesList(files) {
removeAllChildNodes(filesListEl)
files.forEach(({ filename, size, timeModified }, index) => {
const rowEl = document.createElement('tr')
const tdId = document.createElement('td')
const tdName = document.createElement('td')
const tdSize = document.createElement('td')
const tdDate = document.createElement('td')
const tdDelete = document.createElement('td')
tdId.textContent = index + 1
tdName.textContent = filename
tdName.className = 'filename'
tdSize.textContent = size
const formattedDate = new Date(timeModified).toLocaleDateString('sl-SL')
tdDate.textContent = formattedDate
const delBtn = document.createElement('button')
delBtn.className = 'p-0 delete-file delete-btn-table'
delBtn.type = 'button'
const deleteImg = document.createElement('img')
deleteImg.src = '/images/red-trash-icon.svg'
deleteImg.alt = 'Izbriši'
const delSpan = document.createElement('span')
delSpan.className = 'ms-2'
delSpan.textContent = 'Briši'
delBtn.appendChild(deleteImg)
delBtn.appendChild(delSpan)
tdDelete.appendChild(delBtn)
rowEl.append(tdId, tdName, tdSize, tdDate, tdDelete)
filesListEl.appendChild(rowEl)
})
}
function displayFailedUploads(failedUploads) {
failedUploads.forEach(({ filename, message }) => {
const alertText = modalAlert.querySelector('#alert-text')
alertText.textContent = `NAPAKA - Ime datoteke: ${filename}, razlog: ${message}`
modalAlert.toggle()
})
}
async function handleFileClick(e) {
if (e.target.closest('.delete-file')) {
const fileEl = e.target.closest('tr')
const filename = fileEl.querySelector('.filename').textContent
try {
await axios.delete(`${apiEndpointBase}/${filename}`)
fileEl.remove()
} catch {
alert('Pri brisanju datoteke je prišlo do napake.')
}
}
}
@@ -0,0 +1,33 @@
/* global axios, bootstrap */
const extractionListEl = document.getElementById('extraction-list')
extractionListEl.addEventListener('click', onListClick)
async function onListClick({ target }) {
if (target.closest('.btn-delete')) {
const extractionEl = target.closest('.task')
const extractionId = extractionEl.dataset.id
const alertModal = new bootstrap.Modal(
document.getElementById('alert-modal')
)
alertModal.toggle()
const modalUseBtn = document.getElementById('modal-use-btn')
modalUseBtn.addEventListener('click', async () => {
await axios.delete(`/api/v1/extraction/${extractionId}`)
extractionEl.remove()
})
} else if (target.classList.contains('btn-begin')) {
const extractionEl = target.closest('.task')
const extractionId = extractionEl.dataset.id
await axios.put(`/api/v1/extraction/${extractionId}/begin`)
const responseModal = new bootstrap.Modal(
document.getElementById('begin-response')
)
responseModal.toggle()
} else if (target.classList.contains('btn-duplicate')) {
const extractionEl = target.closest('.task')
const extractionId = extractionEl.dataset.id
await axios.post(`/api/v1/extraction/${extractionId}/duplicate`)
}
}
@@ -0,0 +1,79 @@
/* global $, axios, bootstrap */
$('.pick-multiple').select2()
$('.enter-multiple').select2({
tags: true
})
const editStopTermsLink = document.getElementById('edit-stop-terms')
const searchButton = document.getElementById('search-btn')
const searchResultEl = document.getElementById('search-result')
const taskResultEl = document.querySelector('.task')
const resultsCountEl = taskResultEl.querySelector('#results-count')
const formEl = document.getElementById('form-edit-oss')
const extractionId = +location.pathname.split('/').at(-1)
const modalSpinner = new bootstrap.Modal(
document.getElementById('modal-spinner')
)
const modalAlert = new bootstrap.Modal(document.getElementById('alert-modal'))
editStopTermsLink.addEventListener('click', saveOssParamsFirst)
searchButton.addEventListener('click', handleSearch)
searchResultEl.addEventListener('click', handleSearchResultsClick)
async function saveOssParamsFirst() {
const payload = new URLSearchParams(new FormData(formEl))
navigator.sendBeacon(
`/api/v1/extraction/${extractionId}/oss-save-params`,
payload
)
}
async function handleSearch() {
modalSpinner.toggle()
try {
const { data } = await submitSearch()
displaySearchResults(data)
} catch {
handleSearchError()
}
modalSpinner.toggle()
}
function handleSearchError() {
const alertText = modalAlert.querySelector('#alert-text')
alertText.textContent = `NAPAKA pri iskanju`
modalAlert.toggle()
}
async function submitSearch() {
const payload = new URLSearchParams(new FormData(formEl))
return await axios.put(
`/api/v1/extraction/${extractionId}/oss-search`,
payload
)
}
function displaySearchResults({ documentCount, canSave }) {
resultsCountEl.textContent = `${documentCount}`
if (canSave) {
const saveButton = taskResultEl.querySelector('.btn')
saveButton.disabled = false
}
taskResultEl.classList.remove('d-none')
}
function handleSearchResultsClick({ target }) {
if (target.closest('#save-params')) confirmParams()
}
async function confirmParams() {
modalSpinner.toggle()
try {
await axios.put(`/api/v1/extraction/${extractionId}/oss-confirm-params`)
location = '/luscenje'
} catch {
alert('Error saving params')
modalSpinner.toggle()
}
}
@@ -0,0 +1,128 @@
/* global axios */
const fileUploadForm = document.forms['upload-files']
const fileInputEl = fileUploadForm.querySelector('input[type="file"]')
const messageContainerEl = document.getElementById('messages')
const filesListEl = document.getElementById('files-list')
const extractionId = +fileUploadForm.extractionId.value
let apiEndpointBase
switch (location.pathname.split('/').at(-1)) {
case 'besedila':
apiEndpointBase = `/api/v1/extraction/${extractionId}/documents`
break
case 'stop-termini':
apiEndpointBase = `/api/v1/extraction/${extractionId}/stop-terms`
break
default:
throw Error("apiEndpointBase couldn't be determined")
}
fileInputEl.addEventListener('change', submitFiles)
filesListEl.addEventListener('click', handleFileClick)
async function submitFiles() {
// TODO Lock additional submits for the duration of this function execution?
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
const failedUploads = []
displaySpinner()
for (const file of fileInputEl.files) {
if (file.size > MAX_FILE_SIZE) {
const failedUpload = {
filename: file.name,
message: 'File too large. Must not be over 1 GB.'
}
failedUploads.push(failedUpload)
continue
}
const payload = new FormData()
payload.set(fileInputEl.name, file)
try {
await axios.put(apiEndpointBase, payload)
} catch (error) {
const failedUpload = {
filename: file.name,
message: error.response.data
}
failedUploads.push(failedUpload)
}
}
try {
const { data: files } = await axios.get(apiEndpointBase)
updateFilesList(files)
} catch {
alert('Pri posodobljanju seznama naloženih datotek je prišlo do napake.')
}
fileInputEl.value = ''
displayFailedUploads(failedUploads)
hideSpinner()
}
function displaySpinner() {
const messageEl = document.createElement('li')
messageEl.textContent = 'Spinner on'
messageContainerEl.appendChild(messageEl)
}
function hideSpinner() {
const messageEl = document.createElement('li')
messageEl.textContent = 'Spinner off'
messageContainerEl.appendChild(messageEl)
}
function updateFilesList(files) {
removeAllChildNodes(filesListEl)
files.forEach(({ filename, size, timeModified }) => {
const fileEl = document.createElement('li')
const filenameSpanEl = document.createElement('span')
filenameSpanEl.className = 'filename'
filenameSpanEl.textContent = filename
const formattedDate = new Date(timeModified).toLocaleDateString('sl-SL')
const deleteButtonEl = document.createElement('a')
deleteButtonEl.className = 'delete-file'
deleteButtonEl.href = '#'
deleteButtonEl.textContent = 'BRIŠI'
fileEl.append(
'DATOTEKA - Ime: ',
filenameSpanEl,
`, velikost: ${size}, datum: ${formattedDate} `,
deleteButtonEl
)
filesListEl.appendChild(fileEl)
})
}
function displayFailedUploads(failedUploads) {
failedUploads.forEach(({ filename, message }) => {
const messageEl = document.createElement('li')
messageEl.textContent = `NAPAKA - Ime datoteke: ${filename}, razlog: ${message}`
messageContainerEl.appendChild(messageEl)
})
}
async function handleFileClick(e) {
if (e.target.closest('.delete-file')) {
const fileEl = e.target.closest('li')
const filename = fileEl.querySelector('.filename').textContent
try {
await axios.delete(`${apiEndpointBase}/${filename}`)
fileEl.remove()
} catch {
alert('Pri brisanju datoteke je prišlo do napake.')
}
}
}
// Don't copy this one into final JS. It's already defined in scripts.js
function removeAllChildNodes(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild)
}
}
@@ -0,0 +1,22 @@
/* global axios */
const extractionListEl = document.getElementById('extraction-list')
extractionListEl.addEventListener('click', onListClick)
async function onListClick({ target }) {
if (target.classList.contains('btn-delete')) {
const extractionEl = target.closest('li')
const extractionId = extractionEl.dataset.id
await axios.delete(`/api/v1/extraction/${extractionId}`)
extractionEl.remove()
} else if (target.classList.contains('btn-begin')) {
const extractionEl = target.closest('li')
const extractionId = extractionEl.dataset.id
await axios.put(`/api/v1/extraction/${extractionId}/begin`)
} else if (target.classList.contains('btn-duplicate')) {
const extractionEl = target.closest('li')
const extractionId = extractionEl.dataset.id
await axios.post(`/api/v1/extraction/${extractionId}/duplicate`)
}
}
@@ -0,0 +1,95 @@
/* global $, axios */
$('.pick-multiple').select2()
$('.enter-multiple').select2({
tags: true
})
const editStopTermsLink = document.getElementById('edit-stop-terms')
const searchButton = document.getElementById('search-btn')
const searchResultEl = document.getElementById('search-result')
const messageContainerEl = document.getElementById('messages')
const formEl = document.forms[0]
const extractionId = +location.pathname.split('/').at(-1)
editStopTermsLink.addEventListener('click', saveOssParamsFirst)
searchButton.addEventListener('click', handleSearch)
searchResultEl.addEventListener('click', handleSearchResultsClick)
async function saveOssParamsFirst() {
const payload = new URLSearchParams(new FormData(formEl))
navigator.sendBeacon(
`/api/v1/extraction/${extractionId}/oss-save-params`,
payload
)
}
async function handleSearch() {
displaySpinner()
try {
const { data } = await submitSearch()
displaySearchResults(data)
} catch {
handleSearchError()
}
hideSpinner()
}
function displaySpinner() {
const messageEl = document.createElement('li')
messageEl.textContent = 'Spinner on'
messageContainerEl.appendChild(messageEl)
}
function hideSpinner() {
const messageEl = document.createElement('li')
messageEl.textContent = 'Spinner off'
messageContainerEl.appendChild(messageEl)
}
function handleSearchError() {
const messageEl = document.createElement('li')
messageEl.textContent = 'Notify the user of error that occured during search'
messageContainerEl.appendChild(messageEl)
}
async function submitSearch() {
const payload = new URLSearchParams(new FormData(formEl))
return await axios.put(
`/api/v1/extraction/${extractionId}/oss-search`,
payload
)
}
function displaySearchResults({ documentCount, canSave }) {
removeAllChildNodes(searchResultEl)
searchResultEl.textContent = `Število dokumentov: ${documentCount}`
if (canSave) {
const saveButton = document.createElement('button')
saveButton.id = 'save-params'
saveButton.textContent = 'Shrani'
searchResultEl.append(saveButton)
}
}
function handleSearchResultsClick({ target }) {
if (target.closest('#save-params')) confirmParams()
}
async function confirmParams() {
displaySpinner()
try {
await axios.put(`/api/v1/extraction/${extractionId}/oss-confirm-params`)
location = '../poc'
} catch {
alert('Error saving params')
hideSpinner()
}
}
// Don't copy this one into final JS. It's already defined in scripts.js
function removeAllChildNodes(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild)
}
}
@@ -0,0 +1,144 @@
/* global termCandidates, hitsPerPage, numberOfAllPages */
{
const resultsListEl = document.getElementById('page-results')
const updateDemoPager = initPagination('pagination', onPageChange)
function onPageChange(newPage) {
const results = getDataForPage(newPage)
removeAllChildNodes(resultsListEl)
renderResults(results)
updateDemoPager(newPage, numberOfAllPages)
}
function getDataForPage(page) {
const sliceStart = (page - 1) * hitsPerPage
const sliceEnd = page * hitsPerPage
const onePageOfTermCandidates = termCandidates.slice(sliceStart, sliceEnd)
const data = onePageOfTermCandidates.map((candidate, index) => {
const sequentialCount = sliceStart + index + 1
return [sequentialCount, candidate]
})
return data
}
function renderResults(results) {
results.forEach(([sequentialCount, candidate]) => {
const newListEl = document.createElement('li')
newListEl.textContent = `[${sequentialCount}] ${JSON.stringify(
candidate
)}`
resultsListEl.appendChild(newListEl)
})
}
}
// Below code is copied from scripts.js, so it will already be available on the real page. No need to copy it there also.
function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
const rootEl = document.getElementById(paginationRootElId)
const btnFirstPage = rootEl.querySelector('.first-page')
const btnPreviousPage = rootEl.querySelector('.previous-page')
const btnNextPage = rootEl.querySelector('.next-page')
const btnLastPage = rootEl.querySelector('.last-page')
const formEl = rootEl.querySelector('form')
const pageInputEl = formEl.querySelector('input')
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
let reqLock = false
rootEl.addEventListener('click', handleButtonClick)
formEl.addEventListener('submit', handleFormSubmit)
function handleButtonClick({ target }) {
if (reqLock) return
const buttonEl = target.closest(`#${paginationRootElId} button`)
if (!buttonEl) return
const numOfAllPages = +pagesCountDisplayEl.textContent
if (buttonEl.classList.contains('first-page')) {
if (currentPage === 1) return
enableLock()
onPageChange(1)
} else if (buttonEl.classList.contains('previous-page')) {
if (currentPage === 1) return
enableLock()
onPageChange(currentPage - 1)
} else if (buttonEl.classList.contains('next-page')) {
if (currentPage === numOfAllPages) return
enableLock()
onPageChange(currentPage + 1)
} else if (buttonEl.classList.contains('last-page')) {
if (currentPage === numOfAllPages) return
enableLock()
onPageChange(numOfAllPages)
}
}
function handleFormSubmit(e) {
e.preventDefault()
if (reqLock) return
const inputValue = +pageInputEl.value
if (!(inputValue > 0 && inputValue <= pagesCountDisplayEl.textContent)) {
alert('Nepravilna vrednost strani')
pageInputEl.value = currentPage
return
}
enableLock()
onPageChange(inputValue)
}
function enableLock() {
reqLock = true
btnFirstPage.disabled = true
btnPreviousPage.disabled = true
btnNextPage.disabled = true
btnLastPage.disabled = true
pageInputEl.disabled = true
}
function disableLock() {
reqLock = false
btnFirstPage.disabled = false
btnPreviousPage.disabled = false
btnNextPage.disabled = false
btnLastPage.disabled = false
pageInputEl.disabled = false
}
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
disableLock()
if (!newCurrentPage) return
currentPage = newCurrentPage
pageInputEl.value = newCurrentPage
pagesCountDisplayEl.textContent = newNumOfAllPages
if (newCurrentPage === 1) {
btnFirstPage.disabled = true
btnPreviousPage.disabled = true
} else {
btnFirstPage.disabled = false
btnPreviousPage.disabled = false
}
if (newCurrentPage === newNumOfAllPages) {
btnNextPage.disabled = true
btnLastPage.disabled = true
} else {
btnNextPage.disabled = false
btnLastPage.disabled = false
}
}
return updatePagerUi
}
// Helper function to easily remove all child nodes. Useful for pagination.
function removeAllChildNodes(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild)
}
}
@@ -0,0 +1,40 @@
/* global termCandidates, hitsPerPage, numberOfAllPages, initPagination, removeAllChildNodes */
{
const resultsListEl = document.getElementById('page-results')
const updateDemoPager = initPagination('pagination', onPageChange)
function onPageChange(newPage) {
const results = getDataForPage(newPage)
removeAllChildNodes(resultsListEl)
renderResults(results)
updateDemoPager(newPage, numberOfAllPages)
}
function getDataForPage(page) {
const sliceStart = (page - 1) * hitsPerPage
const sliceEnd = page * hitsPerPage
const onePageOfTermCandidates = termCandidates.slice(sliceStart, sliceEnd)
const data = onePageOfTermCandidates.map((candidate, index) => {
const sequentialCount = sliceStart + index + 1
return [sequentialCount, candidate]
})
return data
}
function renderResults(results) {
results.forEach(([sequentialCount, candidate]) => {
const rowEl = document.createElement('tr')
const tdId = document.createElement('td')
const tdName = document.createElement('td')
const tdSize = document.createElement('td')
const tdDate = document.createElement('td')
tdId.textContent = sequentialCount
tdName.textContent = candidate.kanonicnaoblika
tdSize.textContent = candidate.ranking
tdDate.textContent = candidate.pogostostpojavljanja
rowEl.append(tdId, tdName, tdSize, tdDate)
resultsListEl.appendChild(rowEl)
})
}
}
+239
View File
@@ -0,0 +1,239 @@
/* global currentPagePath */
// const currentPagePath = location.pathname
// Temporary workaround (use of currentPagePath).
window.addEventListener('load', () => {
initExtraction()
})
function initExtraction() {
const ce = {}
window.extractionElements = ce
ce.textDescription = document.getElementById('text-description')
ce.headerTitle = document.getElementById('site-header-title')
ce.headerRoot = document.getElementById('header-root')
ce.fixedTopSection = document.getElementById('fixed-top-section')
ce.topContainer = document.getElementsByClassName('top-container')[0]
ce.offsetMain = document.getElementById('offset-main')
window.addEventListener('resize', () => {
adjustOffsetBy()
mobileMoveContent()
})
mobileMoveContent()
adjustOffsetBy()
window.onscroll = hideAndShowHeader
function hideAndShowHeader() {
const { textDescription, headerTitle } = window.extractionElements
if (document.body.clientWidth >= 1200) {
if (document.documentElement.scrollTop < 50) {
textDescription.style.display = 'block'
headerTitle.style.display = 'block'
} else {
textDescription.style.display = 'none'
headerTitle.style.display = 'none'
}
}
}
if (
currentPagePath === '/luscenje' ||
currentPagePath === '/luscenje/id_luscenja/oss'
) {
const { offsetMain } = window.extractionElements
offsetMain.addEventListener('click', handleClick)
function handleClick({ target }) {
const editTask = target.closest('.edit-task')
const deleteTask = target.closest('.delete-task')
const doubleTask = target.closest('.double-task')
if (editTask) {
// alert('Greš na drug page...')
} else if (deleteTask) {
// deleteField(deleteTask)
} else if (doubleTask) {
duplicateTask(doubleTask)
}
}
function duplicateTask(ele) {
const element = ele.closest('.task')
const taskName = element.children[0].children[0].children[0].textContent
const duplicateName = taskName + ' ' + 'copy'
const taskCerif = element.children[0].children[0].children[1].textContent
// Create elements
const taskNewContainer = document.createElement('div')
const divSmFlex = document.createElement('div')
const divSmGrid = document.createElement('div')
const spanTaskName = document.createElement('span')
const spanCerif = document.createElement('span')
const divSmFlexAC = document.createElement('div')
const btnStart = document.createElement('button')
const btnImg = document.createElement('img')
const btnSpan = document.createElement('span')
const hr = document.createElement('hr')
const divSmFlex2 = document.createElement('div')
const divSmFlexACMb = document.createElement('div')
const imgAlertCircle = document.createElement('img')
const spanNew = document.createElement('span')
const divSmFlexACMbMe = document.createElement('div')
const divEditTask = document.createElement('div')
const imgEditAlt = document.createElement('img')
const spanEdit = document.createElement('span')
const divDeleteTask = document.createElement('div')
const imgDeleteAlt = document.createElement('img')
const spanDelete = document.createElement('span')
taskNewContainer.className = 'container-fluid task task-new p-3 mb-4'
divSmFlex.className = 'd-sm-flex justify-content-between'
divSmGrid.className = 'd-sm-grid'
spanTaskName.className = 'bold-weight-black'
spanTaskName.textContent = duplicateName
spanCerif.className = 'normal-gray mt-2 mb-2'
spanCerif.textContent = taskCerif
divSmFlexAC.className = 'd-sm-flex align-items-center'
btnStart.className = 'btn btn-secondary align-items-center d-flex'
btnStart.type = 'button'
btnImg.src = '/images/fi_arrow-right-circle.svg'
btnSpan.className = 'ms-1'
btnSpan.textContent = 'Začni'
hr.className = 'mt-2 mb-3'
divSmFlex2.className = 'd-flex justify-content-between'
divSmFlexACMb.className = 'd-sm-flex align-items-center mb-0'
imgAlertCircle.src = '/images/alert-circle.svg'
spanNew.className = 'normal-gray ms-1'
spanNew.textContent = 'Nov'
divSmFlexACMbMe.className = 'd-sm-flex align-content-center mb-0 me-3'
divEditTask.className = 'align-items-center me-3 edit-task'
imgEditAlt.src = '/images/u_edit-alt.svg'
spanEdit.className = 'ms-1 normal-gray'
spanEdit.textContent = 'Uredi'
divDeleteTask.className = 'ms-3 align-items-center delete-task'
imgDeleteAlt.src = '/images/red-trash-icon.svg'
spanDelete.className = 'ms-1 normal-gray'
spanDelete.textContent = 'Briši'
taskNewContainer.appendChild(divSmFlex)
divSmFlex.appendChild(divSmGrid)
divSmGrid.appendChild(spanTaskName)
divSmGrid.appendChild(spanCerif)
divSmFlex.appendChild(divSmFlexAC)
divSmFlexAC.appendChild(btnStart)
btnStart.appendChild(btnImg)
btnStart.appendChild(btnSpan)
taskNewContainer.appendChild(hr)
taskNewContainer.appendChild(divSmFlex2)
divSmFlex2.appendChild(divSmFlexACMb)
divSmFlexACMb.appendChild(imgAlertCircle)
divSmFlexACMb.appendChild(spanNew)
divSmFlex2.appendChild(divSmFlexACMbMe)
divSmFlexACMbMe.appendChild(divEditTask)
divEditTask.appendChild(imgEditAlt)
divEditTask.appendChild(spanEdit)
divSmFlexACMbMe.appendChild(divDeleteTask)
divDeleteTask.appendChild(imgDeleteAlt)
divDeleteTask.appendChild(spanDelete)
const allTasksEl = document.getElementById('all-tasks')
allTasksEl.insertBefore(taskNewContainer, allTasksEl.firstChild)
}
}
// function deleteField(ele) {
// const element = ele.closest('.task')
// const modalUseBtn = document.getElementById('modal-use-btn')
// modalUseBtn.addEventListener('click', () => {
// element.remove()
// })
// element.remove()
// }
adjustOffsetBy()
}
function adjustOffsetBy() {
const { offsetMain, fixedTopSection } = window.extractionElements
const referenceHeight = fixedTopSection.offsetHeight
const offsetHeader = document.getElementsByClassName('offset-header')
const offsetHeaderPadding = document.getElementById('offset-padding')
const headerPadding = document.getElementById('header-padding')
if (document.body.clientWidth < 1200) {
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
} else {
for (let i = 0; i < offsetHeader.length; i++) {
offsetHeader[i].style.paddingTop = `${referenceHeight}px`
}
if (headerPadding !== null) {
const headerPaddingHeight = headerPadding.offsetHeight
offsetMain.style.paddingTop = `${headerPaddingHeight}px`
}
if (offsetHeaderPadding !== null) {
const offsetHeaderHeight = offsetHeaderPadding.offsetHeight
offsetMain.style.paddingTop = `${referenceHeight + offsetHeaderHeight}px`
}
}
}
function mobileMoveContent() {
const adminNavMobileEl = document.getElementById('admin-nav-mobile')
const mobileRightHolder = document.getElementById('mobile-right-holder')
const secondaryButton = document.querySelector('.header-btn-secondary')
const primaryButton = document.querySelector('.header-btn')
const siteHeading = document.getElementById('site-heading')
const siteHeadingTextContent = siteHeading.textContent
const navTitle = document.getElementById('nav-title')
const headerContainerRight = document.querySelector(
'.header-container-divider-right'
)
if (document.body.clientWidth <= 1200) {
if (secondaryButton) {
adminNavMobileEl.classList.add('align-items-center')
adminNavMobileEl.classList.add('justify-content-between')
mobileRightHolder.appendChild(secondaryButton)
secondaryButton.style.height = '28px'
secondaryButton.style.width = '99px'
secondaryButton.style.marginRight = '10px'
}
if (primaryButton) {
mobileRightHolder.appendChild(primaryButton)
adminNavMobileEl.classList.add('align-items-center')
adminNavMobileEl.classList.add('justify-content-between')
primaryButton.style.height = '28px'
primaryButton.style.width = '99px'
primaryButton.style.marginRight = '10px'
primaryButton.style.whiteSpace = 'nowrap'
}
navTitle.textContent = siteHeadingTextContent
siteHeading.style.display = 'none'
}
if (document.body.clientWidth > 1200) {
if (secondaryButton) {
headerContainerRight.appendChild(secondaryButton)
secondaryButton.style.height = ''
secondaryButton.style.width = ''
secondaryButton.style.marginRight = ''
}
if (primaryButton) {
headerContainerRight.appendChild(primaryButton)
adminNavMobileEl.classList.remove('align-items-center')
adminNavMobileEl.classList.remove('justify-content-between')
primaryButton.style.height = ''
primaryButton.style.width = ''
primaryButton.style.marginRight = ''
primaryButton.style.whiteSpace = ''
}
navTitle.textContent = 'Urejanje'
siteHeading.style.display = 'block'
}
}
+1
View File
@@ -0,0 +1 @@
const currentPagePath = location.pathname
+81
View File
@@ -0,0 +1,81 @@
/* global bootstrap */
window.addEventListener('resize', () => {
adjustOffsetBy()
changeSubareasGroup()
})
adjustOffsetBy()
function adjustOffsetBy() {
const fixedTopSection = document.getElementById('fixed-top-section')
const offsetMain = document.getElementById('offset-main')
const referenceHeight = fixedTopSection.offsetHeight
const offsetHeader = document.getElementsByClassName('offset-header')
const offsetHeaderPadding = document.getElementById('offset-padding')
const headerPadding = document.getElementById('header-padding')
if (document.body.clientWidth < 1200) {
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
} else {
for (let i = 0; i < offsetHeader.length; i++) {
offsetHeader[i].style.paddingTop = `${referenceHeight}px`
}
if (headerPadding !== null) {
const headerPaddingHeight = headerPadding.offsetHeight
offsetMain.style.paddingTop = `${headerPaddingHeight}px`
}
if (offsetHeaderPadding !== null) {
const offsetHeaderHeight = offsetHeaderPadding.offsetHeight
offsetMain.style.paddingTop = `${referenceHeight + offsetHeaderHeight}px`
}
}
}
window.addEventListener('scroll', () => changeSubareasGroup())
window.addEventListener('load', () => changeSubareasGroup())
function changeSubareasGroup() {
const subareaLinks = document.querySelectorAll('.subarea-link')
subareaLinks.forEach(el => {
const x = el.closest('.active')
if (x !== null) {
const sublinkSelected = x.closest('.help-links-subgroup')
sublinkSelected.classList.add('active-subgroup')
if (el.nextElementSibling !== null)
el.nextSibling.style.maxHeight = 'fit-content'
} else {
if (el.nextElementSibling !== null) el.nextSibling.style.maxHeight = null
if (el.parentElement.classList.contains('active-subgroup'))
el.parentElement.classList.remove('active-subgroup')
}
})
}
const navigation = document.getElementById('help-nav-scrollspy')
navigation.addEventListener('click', e => closeIfMobile(e))
function closeIfMobile(e) {
const sideMenu = navigation.parentElement
if (sideMenu.classList.contains('opened')) {
if (e.target.tagName === 'A')
// Call generic function for mobile menu closure from scripts.js
toggleNav('slidable')
}
}
// eslint-disable-next-line no-unused-vars
const scrollSpy = new bootstrap.ScrollSpy(document.body, {
target: '#help-nav-scrollspy',
offset: 180
})
const collapsibleElements = document.querySelectorAll('.help-collapsible')
collapsibleElements.forEach(el =>
el.addEventListener('click', () => {
const elem = el.nextSibling
if (elem.style.maxHeight && !el.classList.contains('active')) {
elem.style.maxHeight = null
} else {
elem.style.maxHeight = 'fit-content'
}
})
)
@@ -0,0 +1,281 @@
/* global axios, validator, removeJumpLogic */
/*
Design logic implementation
Miha Stele, 2022
*/
// helper.js
const regBtn = document.querySelector('#regbtn')
const logintBtn = document.querySelector('#loginbtn')
const regForm = document.querySelector('#register-form')
const loginForm = document.querySelector('#login-form')
const regHeader = document.querySelector('.regtitle')
const loginHeader = document.querySelector('.logintitle')
const regDescription = document.querySelector('.regdesc')
const loginDescription = document.querySelector('.logindesc')
const listenRegisterBtn = event => {
logintBtn.className = logintBtn.className.replace('d-none', '')
regBtn.className = regBtn.className + ' d-none'
regForm.className = regForm.className.replace('d-none', '')
loginForm.className = loginForm.className + ' d-none'
regHeader.className = regHeader.className.replace('d-none', '')
loginHeader.className = loginHeader.className + ' d-none'
regDescription.className = regDescription.className.replace('d-none', '')
loginDescription.className = loginDescription.className + ' d-none'
}
const listenLoginBtn = event => {
regBtn.className = regBtn.className.replace('d-none', '')
logintBtn.className = logintBtn.className + ' d-none'
loginForm.className = loginForm.className.replace('d-none', '')
regForm.className = regForm.className + ' d-none'
loginHeader.className = loginHeader.className.replace('d-none', '')
regHeader.className = regHeader.className + ' d-none'
loginDescription.className = loginDescription.className.replace('d-none', '')
regDescription.className = regDescription.className + ' d-none'
}
regBtn.addEventListener('click', listenRegisterBtn)
logintBtn.addEventListener('click', listenLoginBtn)
/* validation */
// const loginErrorIconList = document.querySelector('.error-login-icon')
/* Note: Below 2 constant label arrays are in order */
const registerErrorLabels = [
'error-username',
'error-name',
'error-surname',
'error-email',
'error-password',
'error-password-repeat'
]
const registerInputLabels = [
'register-username',
'register-name',
'register-surname',
'register-email',
'register-password',
'register-password-repeat'
]
const registerErrorIndicatorpairs = {}
registerErrorLabels.forEach(errorLabel => {
registerErrorIndicatorpairs[errorLabel] = []
registerErrorIndicatorpairs[errorLabel].push(
document.querySelector(`#${errorLabel}`)
)
registerErrorIndicatorpairs[errorLabel].push(
document.querySelector(`.${errorLabel}`)
)
})
/*
updateLoginRegisterErrorView
Shows and hides X icons and error labels of specific inputs in register error view
errorLabel - refrerence of the input label
fail - determines if input is valid, if fail is equal to true, input is invalid
input - id of the input, query selector is used in code. Should be a ref to <p> element
description - optional description to add to the <p> error description
*/
function updateLoginRegisterErrorView(
errorLabel,
fail = true,
description = ''
) {
if (fail) {
registerErrorIndicatorpairs[errorLabel][0].style.visibility = 'visible' // <p>
registerErrorIndicatorpairs[errorLabel][1].style.visibility = 'visible' // <img>
} else {
registerErrorIndicatorpairs[errorLabel][0].style.visibility = 'hidden'
registerErrorIndicatorpairs[errorLabel][1].style.visibility = 'hidden'
}
if (description.length > 0) {
registerErrorIndicatorpairs[errorLabel][0].textContent = description
}
}
/*
Show error message from the label that represents the error returned from the server
Includes error X icons for login
*/
function showErrorReturnedFromServer(
errorLabel,
exception,
description = '',
isLogin = false
) {
const elt = document.getElementById(errorLabel)
if (exception) {
if (description.length > 0) {
elt.textContent = description
}
elt.style.visibility = 'visible'
if (isLogin) {
document.querySelectorAll('.error-login-icon').forEach(e => {
e.style.visibility = 'visible'
})
}
} else {
elt.style.visibility = 'hidden'
if (isLogin) {
document.querySelectorAll('.error-login-icon').forEach(e => {
e.style.visibility = 'hidden'
})
}
}
}
function updateRegisterWindowOnSuccess(
modalBodyIdRef,
modalFooterIdRef,
name,
surname,
days = 7
) {
const body = document.getElementById(modalBodyIdRef)
const footer = document.getElementById(modalFooterIdRef)
body.removeChild(document.querySelector('.pspelogsig-5'))
footer.removeChild(footer.firstElementChild)
footer.appendChild(document.createElement('button'))
const btn = footer.firstElementChild
btn.classList = 'btn btn-secondary text-secondary'
btn.textContent = 'ZAPRI'
btn.ariaLabel = 'Close'
btn.dataset.bsDismiss = 'modal'
const successDescriptionPararaph = body.children[1]
successDescriptionPararaph.textContent = `Pozdravljeni ${name} ${surname},
na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu.
Povezava za potrditev je veljavna ${days} dni.`
}
// login and register
{
const loginForm = document.getElementById('login-form')
loginForm.messageBind = document.getElementById('error-description')
const registerForm = document.getElementById('register-form')
registerForm.messageBind = document.getElementById(
'register-message-container'
)
// const messageContainer = document.getElementById('message-container')
loginForm.addEventListener('submit', submitForm)
registerForm.addEventListener('submit', submitForm)
// TODO Function name is very general. Make sure it doesn't conflict, override or
// TODO get overriden by any other function later and rename or generalize as needed.
async function submitForm(event) {
event.preventDefault()
const payload = Object.fromEntries(new FormData(event.target))
const isRegister = event.target.action.includes('/register')
let failFrontendValidation = false
// console.log(payload)
try {
if (isRegister) {
// validation empty inputs for register
const inputs = []
let iterator = 0
registerInputLabels.forEach(label => {
const curr = document.querySelector(`#${label}`)
inputs.push(curr)
if (curr.value.length < 1) {
updateLoginRegisterErrorView(
registerErrorLabels[iterator],
true,
'Prazno obvezno polje'
)
failFrontendValidation = true
} else {
updateLoginRegisterErrorView(registerErrorLabels[iterator], false)
}
iterator++
})
// end validation for empty inputs in register
// other register validators
if (!validator.isEmail(payload.email)) {
failFrontendValidation = true
updateLoginRegisterErrorView(
'error-email',
true,
'Neveljavna e-pošta'
)
} else {
updateLoginRegisterErrorView('error-email', false)
}
if (!validator.isLength(payload.password, { min: 8 })) {
failFrontendValidation = true
updateLoginRegisterErrorView(
'error-password',
true,
'Geslo je prekratko'
)
} else {
updateLoginRegisterErrorView('error-password', false)
}
if (!validator.equals(payload.password, payload.passwordRepeat)) {
failFrontendValidation = true
updateLoginRegisterErrorView(
'error-password-repeat',
true,
'Geslo se ne ujema'
)
}
}
if (!failFrontendValidation) {
const { data } = await axios.post(event.target.action, payload)
console.log(data)
// TODO HANDLE TRUE OR FALSE USERNAME BELOW
if (isRegister) {
updateRegisterWindowOnSuccess(
'logreg-mb',
'logreg-mf',
payload.firstName,
payload.lastName
)
} else {
window.location.href = '/'
}
}
// event.target.messageBind.textContent = data
} catch (error) {
let message = 'Prišlo je do napake.'
if (error.response) {
message = error.response.data
} else if (error.request) {
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
}
showErrorReturnedFromServer(
event.target.messageBind.id,
true,
message,
!isRegister
)
}
}
}
@@ -0,0 +1,327 @@
/* global $, refreshLabels, setVisibleSuggestionsRoot, closeUtilityContainers, sfm,
inputs */
const dcw = document.documentElement.clientWidth
const SMALLER_THAN_MDEIUM_SCREEN = () => {
const dynamicDcw = document.documentElement.clientWidth
return dynamicDcw < 768
}
const searchFilterDOM = {
pd: $('.select-domain-field'), // legacy word used for domain
sl: $('.select-src-lang-field'),
tl: $('.select-dest-lang-field'),
d: $('.select-dict-field'),
s: $('.select-source-field')
}
// Get the input field
// click on enter and focused on enter classes are not used anymore
const buttonMainSearch = document.querySelector('#search-button-main')
const inputMainSearch = document.querySelector('#search-query')
// Execute a function when the user presses a key on the keyboard
if (buttonMainSearch && inputMainSearch) {
inputMainSearch.addEventListener('keypress', function (event) {
// If the user presses the "Enter" key on the keyboard
if (event.key === 'Enter') {
// Cancel the default action, if needed
event.preventDefault()
// Trigger the button element with a click
buttonMainSearch.click()
}
})
}
/// Secondary searchbar search logic (for asmall search screens)
// A lot of DUPLICATE code, enhance later
// search implementation functions
// Also used in search field
// function calibrateWithDelay() {
// setTimeout(() => {
// moveContainer(rootClass, boundingBoxRect)
// updatePopupDimensions(rootCont, boundingBox)
// }, 20)
// }
function bindKeys(bindTo) {
const bindToElement = document.querySelector(bindTo)
document.querySelectorAll('.kbd-key').forEach(e => {
e.addEventListener('click', function (e) {
const oldFocusIndex = bindToElement.selectionStart
bindToElement.value = `${bindToElement.value.substring(
0,
bindToElement.selectionStart
)}${e.target.textContent}${bindToElement.value.substring(
bindToElement.selectionEnd
)}`
bindToElement.focus()
bindToElement.selectionStart = oldFocusIndex + 1
bindToElement.selectionEnd = oldFocusIndex + 1
// setTimeout(() => {
// // calibrateWithDelay()
// renderSuggestionsVK(bindToElement.value)
// }, 10)
})
})
}
const listenForTyping = function (event) {
// moveContainer(rootClass, boundingBox)
// updatePopupDimensions(rootCont, boundingBox)
if (event.code === 'Enter') {
event.preventDefault()
closeUtilityContainers()
}
/* ************************* suggestions code below ************************* */
// if (init) {
// removeSuggestions(root, childOfRootId)
// } else {
// init = true
// }
// renderSuggestions(
// suggestions.filter(f => {
// /* if (event.target.value.length === 1) {
// return f[0] === event.target.value
// } */
// return (
// f.substring(0, event.target.value.length).toLowerCase() ===
// event.target.value.toLowerCase()
// )
// }),
// root,
// boundingBox,
// 'suggestions-root'
// ) // .filter(event.target), rect, rt)
// setVisibleSuggestionsRoot(true)
// Below code is just calibration of the popup windows
// calibrateWithDelay()
}
const inputQueryMain = document.getElementById('search-query')
const inputQuerySecondary = document.getElementById('search-query-sec')
if (inputQueryMain) {
inputQueryMain.addEventListener('keyup', listenForTyping)
}
if (inputQuerySecondary) {
inputQuerySecondary.addEventListener('keyup', listenForTyping)
}
/*
searchQuery
- searchString - search string provided in the search input
- REMOVED: queryParams - parameters such as filters, etc. "q" is not allowed as query param since search string reserves the q parameter
- searchFilterDOM - jQuery DOM elements due to variable reusability. There will be converted to querparams
*/
function searchQuery(searchString, searchFilterDOM = {}) {
const stringBuilder = `/iskanje?q=${searchString}`
const url = new URL(stringBuilder, location.protocol + '//' + location.host)
// Map filter DOM elements to query parameters
Object.entries(searchFilterDOM).forEach(([k, v]) => {
/* queryParams[k] = v.val().reduce((acc, el, index) => {
if(index == 0)
}) */
if (v) {
v.val().forEach(val => {
url.searchParams.append(k, val)
})
}
})
/*
// Todo - map query names if required
Object.keys(queryParams).forEach(key => {
if (key !== 'q') {
// stringBuilder = `${stringBuilder}&${key}=${queryParams[key]}`
url.searchParams.append(key, queryParams[key])
}
})
*/
document.location.href = url
}
// end search implementation functions
// search filter functions
function sbmFn(sbm) {
if (sbm) {
sbm.addEventListener('click', e => {
let inputString
if (inputQuerySecondary && SMALLER_THAN_MDEIUM_SCREEN()) {
inputString = inputQuerySecondary.value
} else {
inputString = inputQueryMain.value
}
// TODO fill query parameters below for the core search example: domain, dictionary, ...
if (inputString === '') {
inputString = '*'
}
searchQuery(inputString, searchFilterDOM)
})
}
}
//
function handleSSCLocation() {
/*
function removeSSCChild(elt) {
if (elt) {
elt.innerHMTL = ''
}
}
*/
function swapContainer(cnt, rmCnt, elt) {
if (cnt) {
cnt.appendChild(elt)
}
// removeSSCChild(rmCnt)
}
const SSCSmallContainerHolder = document.getElementById('SSCSec')
const SSCContainerHolder = document.getElementById('SSC')
const SSCContainer = document.querySelector('.search-suggestion-container')
if (SMALLER_THAN_MDEIUM_SCREEN()) {
swapContainer(SSCSmallContainerHolder, SSCContainerHolder, SSCContainer)
} else {
swapContainer(SSCContainerHolder, SSCSmallContainerHolder, SSCContainer)
}
}
const focusOnCompletePromptForSmallDevices = () => {
focusOnCompletePromtBase(inputQuerySecondary)
}
const focusOnCompletePrompt = () => {
focusOnCompletePromtBase(inputQueryMain)
}
const focusOnCompletePromtBase = queryInput => {
queryInput.focus()
queryInput.select()
}
// Get the input field
const buttonOnSmallScreen = document.querySelector('#search-button-main-sec')
// Execute a function when the user presses a key on the keyboard
if (buttonOnSmallScreen && inputQuerySecondary) {
inputQuerySecondary.addEventListener('keypress', function (event) {
// If the user presses the "Enter" key on the keyboard
if (event.key === 'Enter') {
// Cancel the default action, if needed
event.preventDefault()
// Trigger the buttonOnSmallScreen element with a click
buttonOnSmallScreen.click()
}
})
}
function submitForSSCButtons(btnQuery, isOnce = true) {
sbmFn(document.querySelector(btnQuery))
if (document.querySelector(btnQuery) && isOnce) {
sbmFn(document.querySelector('.search-btn-a'))
}
}
submitForSSCButtons('#search-button-main')
submitForSSCButtons('#search-button-main-sec', false)
/*
sbmFn(document.querySelector('#search-button-main'))
if (document.querySelector('#search-button-main')) {
sbmFn(document.querySelector('.search-btn-a'))
} */
if (inputQueryMain) {
bindKeys('#search-query')
}
if (inputQuerySecondary) {
bindKeys('#search-query-sec')
}
document.querySelector('.clear-f')?.addEventListener('click', e => {
if (inputQueryMain) {
inputQueryMain.value = ''
}
if (inputQuerySecondary) {
inputQuerySecondary.value = ''
}
Object.values(searchFilterDOM).forEach(element => {
if (element) {
element.val(null).trigger('change')
}
})
})
document.addEventListener('click', event => {
try {
refreshLabels(inputs)
const specificSQMQuery = document.querySelector(
'#search-query.search-input'
)
// console.log(event)
if (event.target.classList[1].slice(-5) === '-sugg') {
specificSQMQuery.value = event.target.textContent
}
// updatePopupDimensions(rootCont, boundingBox)
} catch (e) {
// console.log(e)
// console.log(e)
}
setVisibleSuggestionsRoot(false)
// Below code is just calibration of the popup windows
// moveContainer(rootClass, boundingBoxRect)
// updatePopupDimensions(rootCont, boundingBox)
})
if (sfm) {
sfm.addEventListener('click', focusOnCompletePrompt)
sfm.addEventListener('click', () => {
if (SMALLER_THAN_MDEIUM_SCREEN()) {
focusOnCompletePromptForSmallDevices()
}
})
}
// inital focus on searchbars script closure
// focus on login/register
if (inputQueryMain) {
focusOnCompletePrompt()
if (SMALLER_THAN_MDEIUM_SCREEN()) {
focusOnCompletePromptForSmallDevices()
}
}
window.addEventListener('resize', () => {
handleSSCLocation()
})
$(document).ready(function () {
handleSSCLocation()
})
+103
View File
@@ -0,0 +1,103 @@
const allMixedContentFields = document.querySelectorAll('.mc-field')
allMixedContentFields.forEach(el =>
el.addEventListener('focusin', () => showMCButtons(el))
)
document.addEventListener('keypress', e => {
if (e.key === 'Enter' && e.target.classList.contains('dispatch-tab')) {
const form = event.target.form
const index = Array.prototype.indexOf.call(form, e.target)
form.elements[index + 1]?.focus()
e.preventDefault()
}
})
function showMCButtons(element) {
const parent = element.parentElement.parentElement.parentElement
const btnGrp = parent.querySelector('.mc-buttons-group')
btnGrp.classList.remove('d-none')
const btnGrpChildren = btnGrp.children
const childrenBtns = Array.from(btnGrpChildren)
childrenBtns.forEach(el =>
el.addEventListener(
'click',
() => {
addMixedContentInput(el, element)
},
// TODO Possibly a dirty hack. Look into it at a later time.
{ once: true }
)
)
document.addEventListener('click', function (event) {
if (parent !== event.target && !parent.contains(event.target)) {
btnGrp.classList.add('d-none')
}
})
}
function addMixedContentInput(el, selectedEl) {
const inputText = selectedEl.value
const selectedStart = selectedEl.selectionStart
const selectedEnd = selectedEl.selectionEnd
const selectedText = inputText.slice(
selectedEl.selectionStart,
selectedEl.selectionEnd
)
if (el.classList.contains('mc-bold') && selectedText.length) {
selectedEl.value =
inputText.substring(0, selectedStart) +
'<b>' +
inputText.substring(selectedStart, selectedEnd) +
'</b> ' +
inputText.substring(selectedEnd)
}
if (el.classList.contains('mc-italic') && selectedText.length) {
selectedEl.value =
inputText.substring(0, selectedStart) +
'<i>' +
inputText.substring(selectedStart, selectedEnd) +
'</i> ' +
inputText.substring(selectedEnd)
}
if (el.classList.contains('mc-supscript') && selectedText.length) {
selectedEl.value =
inputText.substring(0, selectedStart) +
'<sup>' +
inputText.substring(selectedStart, selectedEnd) +
'</sup> ' +
inputText.substring(selectedEnd)
}
if (el.classList.contains('mc-subscript') && selectedText.length) {
selectedEl.value =
inputText.substring(0, selectedStart) +
'<sub>' +
inputText.substring(selectedStart, selectedEnd) +
'</sub> ' +
inputText.substring(selectedEnd)
}
if (el.classList.contains('mc-hyperlink') && selectedText.length) {
selectedEl.value =
inputText.substring(0, selectedStart) +
'<link url="">' +
inputText.substring(selectedStart, selectedEnd) +
'</link> ' +
inputText.substring(selectedEnd)
}
if (el.classList.contains('mc-line-break')) {
selectedEl.value =
inputText.substring(0, selectedStart) +
'<br />' +
inputText.substring(selectedEnd)
el.removeEventListener('click', () => addMixedContentInput, false)
}
// selectedEl.focus()
// selectedEl.setSelectionRange(selectedStart, selectedEnd)
}
@@ -0,0 +1,133 @@
/* global axios */
/* async function getDataForPageWithQueryForArrayByID(
page,
searchQuery,
filters = {}
) {
const qParams = new URL(location).searchParams
qParams.set('p', page)
qParams.set('q', searchQuery)
// console.log(filters)
Object.entries(filters).forEach(([k, v]) => {
// console.log(`k=${k}`)
// console.log(`v=${v}`)
v.forEach(element => {
if (qParams.has(k)) {
qParams.append(k, element.id)
} else {
qParams.set(k, element.id)
}
})
})
const url = `/api/v1/search/dictionaries?${qParams}`
return await axios.get(url)
} */
function prepareQueryArrayForArrayWithIDs(page, searchQuery, filters = {}) {
const qParams = new URL(location).searchParams
qParams.set('p', page)
qParams.set('q', searchQuery)
// console.log(filters)
Object.entries(filters).forEach(([k, v]) => {
// console.log(`k=${k}`)
// console.log(`v=${v}`)
v.forEach(element => {
if (qParams.has(k)) {
qParams.append(k, element.id)
} else {
qParams.set(k, element.id)
}
})
})
return qParams
}
async function prepareQueryParamsBasic(queryParams, URLSearchParams = false) {
let qParams
if (URLSearchParams) {
qParams = URLSearchParams
} else {
qParams = new URL(location).searchParams
}
Object.entries(queryParams).forEach(([k, v]) => {
qParams.set(k, v)
})
const url = `/api/v1/search/dictionaries?${qParams}`
return url
}
async function getDataForPageWithQueryForArray(
page,
searchQuery,
filters = {}
) {
const qParams = new URL(location).searchParams
qParams.set('p', page)
qParams.set('q', searchQuery)
// console.log(filters)
Object.entries(filters).forEach(([k, v]) => {
// console.log(`k=${k}`)
// console.log(`v=${v}`)
v.forEach(element => {
if (qParams.has(k)) {
qParams.append(k, element)
} else {
qParams.set(k, element)
}
})
})
const url = `/api/v1/search/dictionaries?${qParams}`
return await axios.get(url)
}
async function getDataForQueryParams(queryParams) {
const qParams = new URL(location).searchParams
Object.entries(queryParams).forEach(([k, v]) => {
qParams.set(k, v)
})
const url = `/api/v1/search/dictionaries?${qParams}`
return await axios.get(url)
}
// NEW METHODS, REFACTOR TO BOTTOM
function prepareQueryParams(queryParams, filters = {}) {
const qParams = new URL(location).searchParams
Object.entries(filters).forEach(([k, v]) => {
// console.log(`k=${k}`)
// console.log(`v=${v}`)
v.forEach(element => {
if (qParams.has(k)) {
qParams.append(k, element.id)
} else {
qParams.set(k, element.id)
}
})
})
Object.entries(queryParams).forEach(([k, v]) => {
qParams.set(k, v)
})
return qParams
}
async function getDataFromURLSearchParams(URLSearchParams) {
const url = `/api/v1/search/dictionaries?${URLSearchParams}`
return await axios.get(url)
}
@@ -0,0 +1,97 @@
// const currentPagePath = location.pathname
window.addEventListener('load', () => {
init()
})
function init() {
const ce = {}
window.resultElements = ce
ce.textDescription = document.getElementById('text-description')
ce.headerTitle = document.getElementById('site-header-title')
ce.fixedTopSection = document.getElementById('fixed-top-section')
ce.offsetMain = document.getElementById('offset-main')
window.addEventListener('resize', () => {
adjustOffsetBy()
hideAndShowHeader()
})
window.onscroll = hideAndShowHeader
function hideAndShowHeader() {
const { textDescription, headerTitle } = window.resultElements
const offsetHeaderPadding = document.getElementById('offset-padding')
if (document.body.clientWidth > 1200) {
if (document.documentElement.scrollTop < 50) {
textDescription.style.display = 'block'
headerTitle.style.display = 'block'
if (offsetHeaderPadding !== null)
offsetHeaderPadding.className = 'header-section-root pb-0'
} else {
textDescription.style.display = 'none'
headerTitle.style.display = 'none'
if (offsetHeaderPadding !== null)
offsetHeaderPadding.className =
'header-section-root offset-padding pb-0'
}
}
}
adjustOffsetBy()
}
function adjustOffsetBy() {
const { offsetMain, fixedTopSection } = window.resultElements
const referenceHeight = fixedTopSection.offsetHeight
const offsetHeader = document.getElementsByClassName('offset-header')
const offsetHeaderPadding = document.getElementById('offset-padding')
const adminNavMobile = document.getElementsByClassName('admin-nav')
const headerPadding = document.getElementById('header-padding')
const keyboard = document.getElementsByClassName('kbd')[0]
const width = document.body.offsetWidth
let offsetKbdConstant = 21
if (width > 1500) {
offsetKbdConstant = 20
} else if (width < 837) {
if (width > 766) {
/* todo */
offsetKbdConstant = 10
keyboard.style.width = keyboard.style.width * 0.7
}
}
const kbdoffset = width / 2 - keyboard.offsetWidth / 2 - offsetKbdConstant
if (width < 767) {
keyboard.style.left = 0
keyboard.style.top = '125px'
keyboard.style.width = '100%'
} else {
keyboard.style.left = `${kbdoffset}px`
}
keyboard.style.height = 'auto'
if (document.body.clientWidth < 1200) {
for (let i = 0; i < adminNavMobile.length; i++) {
adminNavMobile[i].style.paddingTop = `${referenceHeight}px`
// offsetHeaderPadding.style.padding = '40px'
// offsetMain.style.paddingTop = `${referenceHeight + 40}px`
}
if (offsetHeaderPadding !== null)
offsetHeaderPadding.style.paddingTop = '20px'
if (headerPadding !== null) headerPadding.style.paddingTop = '60px'
offsetMain.style.paddingTop = `0px`
} else {
for (let i = 0; i < offsetHeader.length; i++) {
offsetHeader[i].style.paddingTop = `${referenceHeight}px`
}
if (headerPadding !== null) headerPadding.style.paddingTop = '60px'
offsetMain.style.paddingTop = `${referenceHeight}px`
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,263 @@
/* global $, axios, initPagination, removeAllChildNodes, closeUtilityContainers, prepareQueryArrayForArrayWithIDs, getDataFromURLSearchParams, prepareQueryParams, focusOnCompletePrompt */
{
/*
$('tr').click(function (e) {
// console.log(e)
const id = e.currentTarget.id
if (e.currentTarget.id) {
window.location.href = `/slovarji/${id}/o-slovarju?sentFromEntryId=dictsList`
}
})
$('tr').hover(e => $('i', e.target).toggleClass('outline'))
*/
let orderType = 'dictName'
let orderIndex = false // false - ASC, true - DESC
let currentPage = 1
let primaryDomains = []
let qFilter = ''
document
.getElementById('dictionaries-search-form')
.addEventListener('submit', e => {
e.preventDefault()
})
const submitFilter = async e => {
const search = document.getElementById('search-query').value
const domainFilter = {}
const pd = $('#domain-select').select2('data')
pd.forEach(el => {
if (domainFilter.pd) {
domainFilter.pd.push(el.id)
} else {
domainFilter.pd = [el.id]
}
})
if (pd) {
domainFilter.pd = pd // Array of primary domain filter, ex. ['2', '234, '310']
}
primaryDomains = pd
qFilter = search
const preparedQuery = prepareQueryArrayForArrayWithIDs(
1,
search,
domainFilter
)
const res = await getDataFromURLSearchParams(preparedQuery)
const page = +res.headers.page
currentPage = page
const numberOfAllPages = +res.headers['number-of-all-pages']
// const resultsMarkup = res.data
removeAllChildNodes(resultsListEl)
// renderResults(resultsMarkup)
updatePager(page, numberOfAllPages)
renderResults(res.data)
addListenersOnRefresh()
}
document
.querySelector('#search-in-filter-modal')
.addEventListener('click', submitFilter)
const dsb = document.querySelector('#dicts-search-btn')
dsb.addEventListener('click', submitFilter)
dsb.addEventListener('click', focusOnCompletePrompt)
dsb.addEventListener('click', closeUtilityContainers)
const resultsListEl = document.querySelector('.table-dict-list')
const initialPage = +new URL(location).searchParams.get('p') || 1
const updatePager = initPagination('pagination', onPageChange, initialPage)
resultsListEl.addEventListener('click', onResultClick)
async function onPageChange(newPage) {
const receivedPageNumber = await changePage(newPage)
updateUrlAndHistory(receivedPageNumber)
}
async function changePage(newPage) {
try {
const qparams = prepareQueryParams(
{
p: newPage,
orderType,
orderIndex,
q: qFilter
},
{ pd: primaryDomains }
)
const res = await getDataFromURLSearchParams(qparams)
const page = +res.headers.page
currentPage = page
const numberOfAllPages = +res.headers['number-of-all-pages']
const resultsMarkup = res.data
removeAllChildNodes(resultsListEl)
renderResults(resultsMarkup)
updatePager(page, numberOfAllPages)
// important
addListenersOnRefresh()
return page
} 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()
}
}
/* TODO: think of a way to reduce redundancy of the functions below */
async function getDataForPage(page) {
const qParams = new URL(location).searchParams
qParams.set('p', page)
const url = `/api/v1/search/dictionaries?${qParams}`
return await axios.get(url)
}
/*
getDataForPageWithQuery
inputs:
- page: elements displayed in the page specified
- searchQuery: search string
- filters: filter elements o be added,
format must be as following
{
FILTER_NAME_1: [FILTER_1, FILTER_2, ..., FILTER_N],
FILTER_NAME_2: [...]
}
*/
function renderResults(resultsMarkup) {
const wrapper = document.createElement('div')
wrapper.innerHTML = resultsMarkup
resultsListEl.appendChild(wrapper)
}
function updateUrlAndHistory(page) {
const newUrl = new URL(location)
newUrl.searchParams.set('p', page)
history.pushState(null, '', newUrl)
}
window.addEventListener('popstate', () => {
const page = +new URL(location).searchParams.get('p') || 1
changePage(page)
})
function onResultClick(e) {
const entryEl = e.target.closest(`#${this.id} a.rl`)
if (!entryEl) return
const anchorId = entryEl.querySelector('.anchor').id
const anchoredUrl = new URL(location)
anchoredUrl.hash = anchorId
history.replaceState(null, '', anchoredUrl)
}
// order type -> what to order
// order index -> 0 - none, 1 - ASC, 2 - DESC
async function sortQuery(orderType, orderIndex) {
const queryParams = prepareQueryParams(
{
p: currentPage,
orderType,
orderIndex,
q: qFilter
},
{ pd: primaryDomains }
)
/// copied code, OPTIMIZE
const res = await getDataFromURLSearchParams(queryParams)
const page = +res.headers.page
const numberOfAllPages = +res.headers['number-of-all-pages']
// const resultsMarkup = res.data
removeAllChildNodes(resultsListEl)
// renderResults(resultsMarkup)
updatePager(page, numberOfAllPages)
renderResults(res.data)
}
// non-pure fn
async function sortPlaceholder(name) {
if (orderType === name) {
orderIndex = !orderIndex
} else {
orderType = name
orderIndex = false
}
// reset on begin
await sortQuery(orderType, orderIndex)
addListenersOnRefresh()
// Ordering of code required because we apply this to the new table
// Get the immage ref to toggle
// const indicatorImageDict = document.getElementById('dictNameImg')
// const indicatorImageDomain = document.getElementById('domainNameImg')
// indicatorImageDict.className = 'invisible'
// indicatorImageDomain.className = 'invisible'
const indicatorImage = document.getElementById(`${name}Img`)
if (orderIndex) {
indicatorImage.className = ''
indicatorImage.src = 'images/arrow_drop_up.svg'
} else {
indicatorImage.className = ''
indicatorImage.src = 'images/arrow_drop_down.svg'
}
}
function sortName() {
sortPlaceholder('dictName')
}
function sortDomain() {
sortPlaceholder('domainName')
}
function addListenersOnRefresh() {
document.getElementById('sortByDictName').addEventListener('click', e => {
sortName()
})
document.getElementById('sortByDomainName').addEventListener('click', e => {
sortDomain()
})
}
// initialize with listeners and DESC image
const indicatorImageDict = document.getElementById('dictNameImg')
indicatorImageDict.className = ''
addListenersOnRefresh()
}
@@ -0,0 +1,12 @@
/* global adjustOffsetBy, moveContent */
function mobileMoveContent() {
moveContent('admin-nav-mobile')
}
window.addEventListener('load', () => {
try {
adjustOffsetBy()
mobileMoveContent()
} catch (e) {}
})
@@ -0,0 +1,8 @@
function mobileMoveContentInit() {
mobileMoveContent()
}
window.addEventListener('load', () => {
adjustOffsetBy()
mobileMoveContentInit()
})
@@ -0,0 +1,553 @@
/* global $, axios, currentPagePath, initPagination, removeAllChildNodes, transferText, tooltipTriggerList, tooltipList, reinitalizeDefaultTooltipSet, createTooltip */
// position correction functions
function adjustOffsetBy() {
// const { offsetMain, fixedTopSection } = window.dictionaryElements
const offsetMain = document.querySelector('#offset-main')
const fixedTopSection = document.querySelector('#fixed-top-section')
const referenceHeight = fixedTopSection.offsetHeight
const offsetHeader = document.getElementsByClassName('offset-header')
const offsetHeaderPadding = document.getElementById('offset-padding')
// const adminNavMobile = document.getElementsByClassName('admin-nav')
const headerPadding = document.getElementById('header-padding')
if (document.body.clientWidth < 1200) {
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
} else {
for (let i = 0; i < offsetHeader.length; i++) {
offsetHeader[i].style.paddingTop = `${referenceHeight}px`
}
if (headerPadding !== null) {
const headerPaddingHeight = headerPadding.offsetHeight
offsetMain.style.paddingTop = `${headerPaddingHeight}px`
}
if (offsetHeaderPadding !== null) {
const offsetHeaderHeight = offsetHeaderPadding.offsetHeight
offsetMain.style.paddingTop = `${referenceHeight + offsetHeaderHeight}px`
}
}
}
/*
function moveContent(adminNavClassId) {
if (document.body.clientWidth <= 1200) {
const adminNavMobileEl = document.getElementById(adminNavClassId)
const mobileRightHolder = document.getElementById('mobile-right-holder')
const headerContent = document.getElementById('offset-padding')
const secondaryButton = headerContent.querySelector('.btn-secondary')
const primaryButton = headerContent.querySelector('.btn-primary')
// const siteHeading = document.getElementById('site-heading')
// const siteHeadingTextContent = siteHeading.textContent
// const navTitle = document.getElementById('nav-title')
// const navTitleTextContent = navTitle.textContent
if (secondaryButton) {
mobileRightHolder.appendChild(secondaryButton)
// secondaryButton.style.height = '28px'
// secondaryButton.style.width = '99px'
// secondaryButton.style.marginRight = '10px'
}
if (primaryButton) {
// if (currentPagePath === '/admin/povezave/seznam') {
// primaryButton.style.width = '160px'
// } else {
// primaryButton.style.width = '99px'
// }
mobileRightHolder.appendChild(primaryButton)
adminNavMobileEl.classList.add('align-items-center')
adminNavMobileEl.classList.add('justify-content-between')
// primaryButton.style.height = '28px'
// primaryButton.style.marginRight = '10px'
primaryButton.style.whiteSpace = 'nowrap'
}
// navTitle.textContent = siteHeadingTextContent
// siteHeading.style.display = 'none'
}
}
*/
function mobileMoveContent() {
const adminNavMobileEl = document.getElementById('admin-nav-mobile')
const mobileRightHolder = document.getElementById('mobile-right-holder')
const secondaryButton = document.querySelector('.header-btn-secondary')
const primaryButton = document.querySelector('.header-btn')
const siteHeading = document.getElementById('site-heading')
const siteHeadingTextContent = siteHeading.textContent
const navTitle = document.getElementById('nav-title')
const headerContainerRight = document.querySelector(
'.header-container-divider-right'
)
try {
if (document.body.clientWidth <= 1200) {
if (secondaryButton) {
mobileRightHolder.appendChild(secondaryButton)
// secondaryButton.style.height = '28px'
// secondaryButton.style.width = '99px'
// secondaryButton.style.marginRight = '10px'
}
if (primaryButton) {
mobileRightHolder.appendChild(primaryButton)
adminNavMobileEl.classList.add('align-items-center')
adminNavMobileEl.classList.add('justify-content-between')
// primaryButton.style.height = '28px'
// primaryButton.style.width = '99px'
// primaryButton.style.marginRight = '10px'
primaryButton.style.whiteSpace = 'nowrap'
}
navTitle.textContent = siteHeadingTextContent
siteHeading.style.display = 'none'
}
if (document.body.clientWidth > 1200) {
if (secondaryButton) {
headerContainerRight.appendChild(secondaryButton)
secondaryButton.style.height = ''
secondaryButton.style.width = ''
secondaryButton.style.marginRight = ''
}
if (primaryButton) {
headerContainerRight.appendChild(primaryButton)
adminNavMobileEl.classList.remove('align-items-center')
adminNavMobileEl.classList.remove('justify-content-between')
primaryButton.style.height = ''
primaryButton.style.width = ''
primaryButton.style.marginRight = ''
primaryButton.style.whiteSpace = ''
}
navTitle.textContent = 'Urejanje'
siteHeading.style.display = 'block'
}
} catch (e) {}
}
const resultsListEl = document.getElementById('results-data')
if (resultsListEl) {
resultsListEl.addEventListener('click', onResultClick)
}
const initialPage = +new URL(location).searchParams.get('p') || 1
const updatePager = initPagination('pagination', onPageChange, initialPage)
async function onPageChange(newPage) {
const receivedPageNumber = await changePage(newPage)
updateUrlAndHistory(receivedPageNumber)
}
async function changePage(newPage) {
try {
const res = await getDataForPage(newPage)
const page = +res.headers.page
const numberOfAllPages = +res.headers['number-of-all-pages']
const resultsMarkup = res.data
removeAllChildNodes(resultsListEl)
renderResults(resultsMarkup)
updatePager(page, numberOfAllPages)
reinitalizeDefaultTooltipSet()
/*
try {
textsScreenManager.manageResize()
} catch (e) {
console.log(
'textsScreenManager not initialized or there is an internal error'
)
} */
return page
} catch (error) {
// console.log(error)
let message = 'Prišlo je do napake.'
if (error.response?.data) {
message = error.response.data
} else if (error.request) {
return // return to bypass error caused (assumed) by popstate on iOS/MacOS
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
}
alert(message)
updatePager()
}
}
async function getDataForPage(page) {
const qParams = new URL(location).searchParams
qParams.set('p', page)
const url = `/api/v1/search/main?${qParams}`
return await axios.get(url)
}
function renderResults(resultsMarkup) {
const wrapper = document.createElement('div')
wrapper.innerHTML = resultsMarkup
resultsListEl.appendChild(wrapper)
}
function updateUrlAndHistory(page) {
const newUrl = new URL(location)
newUrl.searchParams.set('p', page)
history.pushState(null, '', newUrl)
}
window.addEventListener('popstate', () => {
const page = +new URL(location).searchParams.get('p') || 1
changePage(page)
})
function onResultClick(e) {
const entryEl = e.target.closest(`#${this.id} a.rl`)
if (!entryEl) return
const anchorId = entryEl.querySelector('.anchor').id
const anchoredUrl = new URL(location)
anchoredUrl.hash = anchorId
history.replaceState(null, '', anchoredUrl)
}
try {
if (tooltipTriggerList.length > 0) {
// initialize tooltips
tooltipList(null, 'gray-tooltip') // ADD for tooltip debug in the end -> [0].show()
}
} catch (e) {}
/*
// refresh: bool
function largeStringsOnSmallScreen(alwaysRefresh) {
let headwordTexts
let translationWords
let synonymWords
function init() {
headwordTexts = [...$('.rihw')]
translationWords = [...$('.term-h')].map(e => {
return e.children[0]
})
synonymWords = [...$('.syn-h'), ...$('.risy')]
}
function setupToolTips() {
setDynamicTooltipList([])
if (headwordTexts) {
headwordTexts.map(ttElement =>
createTooltip(ttElement, ttElement.innerHTML, '')
)
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
}
if (translationWords) {
translationWords.map(ttElement =>
createTooltip(ttElement, ttElement.innerHTML, '')
)
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
}
if (synonymWords) {
synonymWords.map(ttElement =>
createTooltip(ttElement, ttElement.innerHTML, '')
)
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
}
// console.log(dynamicTooltipList)
}
if (!headwordTexts || !translationWords || !synonymWords || alwaysRefresh) {
init()
}
function initTooltips() {
try {
// if (dynamicTooltipList.length || alwaysRefresh) {
setupToolTips()
// }
} catch (e) {}
}
function removeTooltips() {
try {
$('.rihw').tooltip('dispose')
$('.term-h').children().tooltip('dispose')
$('.syn-h').tooltip('dispose')
$('.risy').tooltip('dispose')
} catch (e) {}
}
function manageResize() {
const tmpTerms = [...$('.term-h')].map(e => {
return e.children[0]
})
const elements = [...tmpTerms, ...$('.syn-h')]
// console.log(document.documentElement.clientWidth)
const dcw = document.documentElement.clientWidth
const checkBorders = (dcw > 991 && dcw < 1600) || dcw < 370
if (checkBorders) {
const TEXT_LIMIT = 17
elements.forEach(el => {
el.dataset.textStore = el.innerHTML.includes('...')
? el.dataset.textStore
: el.innerHTML
el.innerHTML = `${el.innerHTML.slice(0, TEXT_LIMIT)}${
el.innerHTML.length > TEXT_LIMIT ? '...' : ''
}`
})
initTooltips()
} else {
elements.forEach(el => {
if (el.dataset.textStore) {
el.innerHTML = el.dataset.textStore
}
})
removeTooltips()
}
}
return {
init: function () {
init()
},
refresh: function () {
init()
try {
setupToolTips()
} catch (e) {}
},
manageResize: manageResize
}
}
const textsScreenManager = largeStringsOnSmallScreen(true)
textsScreenManager.manageResize()
window.onresize = textsScreenManager.manageResize
*/
// refresh: bool
function largeStringsOnSmallScreen(alwaysRefresh) {
let headwordTexts
let translationWords
let synonymWords
function init() {
headwordTexts = [...$('.rihw')]
translationWords = [...$('.term-h')].map(e => {
return e.children[0]
})
synonymWords = [...$('.syn-h'), ...$('.risy')]
}
/*
function setupToolTips() {
setDynamicTooltipList([])
if (headwordTexts) {
headwordTexts.map(ttElement =>
createTooltip(ttElement, ttElement.innerHTML, '')
)
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
}
if (translationWords) {
translationWords.map(ttElement =>
createTooltip(ttElement, ttElement.innerHTML, '')
)
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
}
if (synonymWords) {
synonymWords.map(ttElement =>
createTooltip(ttElement, ttElement.innerHTML, '')
)
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
}
// console.log(dynamicTooltipList)
} */
if (!headwordTexts || !translationWords || !synonymWords || alwaysRefresh) {
init()
}
function createTooltipEl(ttElement, styles) {
// setDynamicTooltipList([])
try {
createTooltip(ttElement, ttElement.innerHTML, styles)
} catch (e) {
console.log(e)
}
}
function removeTooltip(ttElement) {
try {
ttElement.tooltip('dispose')
} catch (e) {}
}
/*
function initTooltips() {
try {
// if (dynamicTooltipList.length || alwaysRefresh) {
setupToolTips()
// }
} catch (e) {}
}
function removeTooltips() {
try {
$('.rihw').tooltip('dispose')
$('.term-h').children().tooltip('dispose')
$('.syn-h').tooltip('dispose')
$('.risy').tooltip('dispose')
} catch (e) {}
}
*/
/* let OLD_TEXT_LIMIT = 5000 // Guard to not prevent overdisposing
function manageResize() {
const tmpTerms = [...$('.term-h')].map(e => {
return e.children[0]
})
const elements = [...tmpTerms, ...$('.syn-h')]
// console.log(document.documentElement.clientWidth) */
/*
$('.word-constraint').each((i, element) => {
// WRONG const wordBr = $(`.word-constraint:nth-child(${i}) > .word-breakable`)
const wordList = $(element).find('.word-breakable')
wordList.each((i, wordElement) => {
// console.log(wordElement.innerText)
if (
wordElement.clientWidth > element.clientWidth &&
wordElement.innerText.split(' ').length <= 1
) {
console.log('Lion')
createTooltip($(wordElement))
wordElement.classList.push('text-ellipsis')
} else {
removeTooltip($(wordElement))
// console.log(wordElement.classList)
wordElement.className = Array.from(wordElement.classList)
.filter(e => e !== 'text-ellipsis')
.join(' ')
// console.log(wordElement.classList)
}
})
// console.log(wordBr)
})
*/
/* const dcw = document.documentElement.clientWidth
// const checkBorders = (dcw > 420 && dcw < 750) || dcw < 370
let borderIndex
// check _common.scc long-text-strip and make sure the widths are aligned
if (dcw < 420) {
borderIndex = 0 // width = 8 chars (<420)
} else if (dcw < 750 || (dcw > 990 && dcw < 1750)) {
borderIndex = 1 // width = 12 chars (420 - 750, 990-1750)
} else if (dcw > 1750) {
borderIndex = 3 // width = 100% (>1759)
} else {
borderIndex = 2 // width = 25 chars (750-990)
}
let TEXT_LIMIT
if (borderIndex === 0) {
TEXT_LIMIT = 8
} else if (borderIndex === 1) {
TEXT_LIMIT = 12
} else if (borderIndex === 2) {
TEXT_LIMIT = 25
} else {
TEXT_LIMIT = null
}
if (OLD_TEXT_LIMIT !== TEXT_LIMIT) {
elements.forEach(el => {
// console.log(el.innerHTML)
if (TEXT_LIMIT && el.innerHTML.length > TEXT_LIMIT) {
console.log(`DEBUG TEXT LIMIT: ${TEXT_LIMIT}`)
console.log(`DEBUG EL LEN: ${el.innerHTML.length}`)
console.log(`DEBUG EL TEXT: ${el.innerHTML}`)
console.log('TOOLTIP CREATED')
createTooltipEl(el, 'gray-tooltip')
} else {
removeTooltip($(el))
}
})
}
OLD_TEXT_LIMIT = TEXT_LIMIT */
/*
if (checkBorders) {
console.log('HEYOO')
const TEXT_LIMIT = 17
elements.forEach(el => {
el.dataset.textStore = el.innerHTML.includes('...')
? el.dataset.textStore
: el.innerHTML
el.innerHTML = `${el.innerHTML.slice(0, TEXT_LIMIT)}${
el.innerHTML.length > TEXT_LIMIT ? '...' : ''
}`
})
initTooltips()
} else {
elements.forEach(el => {
if (el.dataset.textStore) {
el.innerHTML = el.dataset.textStore
}
})
removeTooltips()
}
*/
/* }
return {
init: function () {
init()
},
refresh: function () {
init()
},
manageResize: manageResize
} */
}
// const textsScreenManager = largeStringsOnSmallScreen(true)
// textsScreenManager.manageResize()
// window.onresize = textsScreenManager.manageResize
/*
function transferText(sideMenuText) {
const siteHeading = document.getElementById('site-heading')
const siteHeadingTextContent = siteHeading.textContent
const navTitle = document.getElementById('nav-title')
if (document.body.clientWidth <= 1200) {
navTitle.textContent = siteHeadingTextContent
siteHeading.style.display = 'none'
} else {
navTitle.textContent = sideMenuText
}
}
*/
function handleProperTextDisplay() {
if (/\/iskanje/.test(currentPagePath)) {
transferText('Iskanje po slovarjih', true)
} else if (/\/termin/.test(currentPagePath)) {
transferText('', true)
}
}
window.addEventListener('resize', () => {
handleProperTextDisplay()
adjustOffsetBy()
})
handleProperTextDisplay()
+206
View File
@@ -0,0 +1,206 @@
/* global $, currentPagePath */
// const currentPagePath = location.pathname
window.addEventListener('load', () => {
initSelect()
})
function initSelect() {
const ce = {}
window.adminElements = ce
ce.textDescription = document.getElementById('text-description')
ce.headerTitle = document.getElementById('site-header-title')
ce.fixedTopSection = document.getElementById('fixed-top-section')
ce.offsetMain = document.getElementById('offset-main')
window.addEventListener('resize', () => {
adjustOffsetBy()
hideAndShowHeader()
})
window.onscroll = hideAndShowHeader
function hideAndShowHeader() {
const { textDescription, headerTitle } = window.adminElements
const offsetHeaderPadding = document.getElementById('offset-padding')
if (document.body.clientWidth > 1200) {
if (document.documentElement.scrollTop < 50) {
textDescription.style.display = 'block'
headerTitle.style.display = 'block'
if (offsetHeaderPadding !== null)
offsetHeaderPadding.className = 'header-section-root pb-0'
} else {
textDescription.style.display = 'none'
headerTitle.style.display = 'none'
if (offsetHeaderPadding !== null)
offsetHeaderPadding.className =
'header-section-root offset-padding pb-0'
}
}
}
if (currentPagePath === '/admin/povezave/seznam') {
const { offsetMain } = window.adminElements
offsetMain.addEventListener('click', handleClick)
}
if (currentPagePath === '/admin/uporabniki/id_uporabnika/urejanje') {
ce.userPasswordEl = document.getElementById('user-password')
ce.userConfirmationPassEl = document.getElementById('user-conf-password')
ce.buttonSaveEl = document.querySelectorAll('.btn-primary')
ce.buttonSaveEl.forEach(el =>
el.addEventListener('click', passwordMatchCheck)
)
// ce.buttonSaveEl.addEventListener('click', passwordMatchCheck)
function passwordMatchCheck() {
const userPassword1 = ce.userPasswordEl.value
const userPassword2 = ce.userConfirmationPassEl.value
if (userPassword1 === '') {
alert('Prosim, vnesite geslo')
} else if (userPassword2 === '') {
alert('Prosim, ponovno vnesite geslo')
} else if (userPassword1 !== userPassword2) {
alert('Gesli se ne ujemata')
} else {
alert('Gesli se ujemata!')
}
}
}
adjustOffsetBy()
}
function adjustOffsetBy() {
const { offsetMain, fixedTopSection } = window.adminElements
const referenceHeight = fixedTopSection.offsetHeight
const offsetHeader = document.getElementsByClassName('offset-header')
const offsetHeaderPadding = document.getElementById('offset-padding')
const adminNavMobile = document.getElementsByClassName('admin-nav')
const headerPadding = document.getElementById('header-padding')
if (document.body.clientWidth < 1200) {
for (let i = 0; i < adminNavMobile.length; i++) {
adminNavMobile[i].style.paddingTop = `${referenceHeight}px`
// offsetHeaderPadding.style.padding = '40px'
// offsetMain.style.paddingTop = `${referenceHeight + 40}px`
}
if (offsetHeaderPadding !== null)
offsetHeaderPadding.style.paddingTop = '20px'
if (headerPadding !== null) headerPadding.style.paddingTop = '60px'
offsetMain.style.paddingTop = `0px`
} else {
for (let i = 0; i < offsetHeader.length; i++) {
offsetHeader[i].style.paddingTop = `${referenceHeight}px`
}
if (headerPadding !== null) headerPadding.style.paddingTop = '60px'
offsetMain.style.paddingTop = `${referenceHeight + 110}px`
}
}
function handleClick({ target }) {
const deleteTask = target.closest('.delete-task')
if (deleteTask) {
deleteField(deleteTask)
}
}
function deleteField(ele) {
const element = ele.closest('.task')
element.remove()
}
// Summernote
$('.summernote').summernote({
placeholder: 'Na kratko opišite zasnovo in namen slovarja.',
height: 300,
minheight: 150,
toolbar: [
['style', ['style', 'bold', 'italic', 'underline']],
['font', ['superscript', 'subscript']],
['link', ['linkDialogShow']],
['para', ['ul', 'ol']],
['table', ['table']],
['insert', ['picture']]
],
styleTags: ['p', 'h3', 'h4']
})
// ELEMENT DEFINITIONS
const cbox = document.createElement('input')
cbox.setAttribute('type', 'checkbox')
cbox.checked = true
const arrow = document.createElement('img')
arrow.setAttribute('src', 'images/chevron-right.svg')
// let clicked = false
let clickedId = 'none'
// END ELEMENT DEFINITIONS
/// FUNCTION DEFINITIONS
const createCheckBox = () => {
const cbox = document.createElement('input')
cbox.setAttribute('type', 'checkbox')
cbox.checked = true
return cbox
}
const createArrowImg = () => {
const arrow = document.createElement('img')
arrow.setAttribute('src', 'images/chevron-right.svg')
return arrow
}
// const clickLanguageSelectSimple = e => {
// // https://stackoverflow.com/questions/38861601/how-to-only-trigger-parent-click-event-when-a-child-is-clicked/38861760
// const child = e.currentTarget.childNodes[0].childNodes[0]
// if (clicked) {
// e.currentTarget.childNodes[0].replaceChild(arrow, child)
// } else {
// e.currentTarget.childNodes[0].replaceChild(cbox, child)
// }
// console.log(e.currentTarget.children[0][0])
// clicked = !clicked
// }
const clickLanguageSelect = e => {
const target = e.currentTarget
const child = target.childNodes[0].childNodes[0]
// console.log(`rootchild: ${child}`)
if (clickedId === 'none') {
// console.log(`child: ${child}`)
target.childNodes[0].replaceChild(createCheckBox(), child)
clickedId = target.id
} else if (clickedId === target.id) {
// console.log(`child: ${child}`)
target.childNodes[0].replaceChild(createArrowImg(), child)
clickedId = 'none'
} else {
const curr = document.getElementById(clickedId)
const currchild = curr.childNodes[0].childNodes[0]
curr.childNodes[0].replaceChild(createArrowImg(), currchild)
target.childNodes[0].replaceChild(createCheckBox(), child)
clickedId = target.id
// console.log(`curr: ${currchild}`)
// console.log(`child: ${child}`)
}
}
/// END FUNCTION DEFINITION
// TESTS
const siEltTEST = document.getElementById('sl')
siEltTEST.onclick = clickLanguageSelect
const hrElttest = document.getElementById('hr')
hrElttest.onclick = clickLanguageSelect
const itEltTEST = document.getElementById('it')
itEltTEST.onclick = clickLanguageSelect
@@ -0,0 +1,433 @@
/* global axios */
/*
TODO
Naming convention for this file is not the best :/, due to filter extension it is
not just language filter, but filter for many properties...
*/
/** BEGIN VARIABLE CONSTRUCTION AREA */
// TODO insert all sections of ids
const ids = [
'src-lang-side',
'dest-lang-side',
'domain-side',
'dict-side',
'source-side'
]
// const showAllAnchors = [
// 'show-all-src-lang-side',
// 'show-all-dest-lang-side',
// 'show-all-dict-side'
// ]
/*
extracts entries from the navigation from the ID (without the hash)
*/
/*
function extract(sectionId) {
try {
const doc = document.querySelector(`#${sectionId}.nav-section-content`)
const entries = doc.querySelectorAll('.nav-section-content-desc')
return {
id: sectionId,
items: Array.from(entries).map(a => a.innerHTML)
}
} catch (e) {}
}
*/
function extractMap(sectionId) {
try {
const doc = document.querySelector(`#${sectionId}.nav-section-content`)
const entries = doc.querySelectorAll('.nav-section-content-desc')
return {
id: sectionId,
items: Array.from(entries).map(a => {
return {
name: a.innerHTML,
count: a.parentElement.children[2].innerHTML,
id: a.parentNode.parentNode.id
}
})
}
} catch (e) {}
}
/// / Dummy hardcoded variables
const sourceLanguageList = extractMap(ids[0])
const destinationLanguageList = extractMap(ids[1])
const domainsList = extractMap(ids[2])
const dictsList = extractMap(ids[3])
const sourcesList = extractMap(ids[4])
const initializedValues = {
'src-lang-side': sourceLanguageList,
'dest-lang-side': destinationLanguageList,
'domain-side': domainsList,
'dict-side': dictsList,
'source-side': sourcesList
}
const selectedIds = {
/*
'src-lang-side': [],
'dest-lang-side': [],
'domain-side': [],
'dict-side': [],
'source-side': []
*/
}
function fillSelectedID(idSec) {
Array.from(document.getElementById(idSec).children).forEach(child => {
if (child.tagName === 'LI') {
if (
child.children[0].children[0].children[0].getAttribute('src') ===
'/images/square-checkbox-solid.svg'
) {
selectedIds[idSec].push(child.id)
}
}
})
}
function fillSelectedIDs() {
ids.forEach(idSec => {
fillSelectedID(idSec)
})
}
function initSelectedIds() {
ids.forEach(idSec => {
selectedIds[idSec] = []
})
}
initSelectedIds()
fillSelectedIDs()
/** referenced to: select more buttons */
const selectMoreIDs = [
'select-src-langs',
'select-dest-langs',
'select-domains',
'select-dicts',
'select-sources'
]
const queryOrderedArr = {
'src-lang-side': 'sl',
'dest-lang-side': 'tl',
'domain-side': 'pd',
'dict-side': 'd',
'source-side': 's'
}
const url = new URL(window.location.href)
/** ID of the filter section */
let sectionIDBattery = -1
/** END VARIABLE CONSTRUCTION AREA */
function createListItemBlockForModal(id, name, amount, sectionId) {
const li = document.createElement('li')
const div = document.createElement('div')
li.appendChild(div)
const input = document.createElement('input')
input.type = 'checkbox'
input.id = id
if (selectedIds[sectionId].filter(e => e === id).length > 0) {
input.checked = true
}
div.appendChild(input)
const label = document.createElement('label')
label.setAttribute('for', id)
label.innerHTML = name
label.classList = 'nav-modal-content-desc checked'
li.appendChild(label)
const amnt = document.createElement('span')
amnt.innerHTML = amount
label.appendChild(amnt)
return li
}
function linkToModal(element) {
const modalListRoot = document.querySelector('#modal-list')
modalListRoot.appendChild(element)
}
function initModalList(id, list) {
list.forEach(l =>
linkToModal(createListItemBlockForModal(l.id, l.name, l.hits, id))
)
sectionIDBattery = id
}
function cleanOldList() {
const modalListRoot = document.querySelector('#modal-list')
modalListRoot.innerHTML = ''
}
async function selectMoreListener(event) {
const idstring = `${event.currentTarget.id
.replace('select-', '')
.slice(0, -1)
.concat('-side')}`
// console.log(extractMap(idstring))
// mp is the array of the currently displayed filters (required to get the checked information)
// const mp = initializedValues[idstring]
// all filters for search
// const allAggregationData = { id: idstring }
const allAggregationData = { id: idstring }
const currentURL = new URL(window.location.href)
const searchParams = currentURL.searchParams
searchParams.set('selectedFilter', idToSearchFiltersMapper(idstring))
const url = `/api/v1/search/term-filters?${searchParams.toString()}`
const { data } = await axios.get(url)
// const parsed = JSON.parse(event.currentTarget.dataset.aggregationInfo ?? '[]')
allAggregationData.items = data[idToSearchFiltersMapper(idstring)]
cleanOldList()
initModalList(allAggregationData.id, allAggregationData.items)
}
function idToSearchFiltersMapper(inpt) {
const mappings = {
'src-lang-side': 'sourceLanguages',
'dest-lang-side': 'targetLanguages',
'domain-side': 'primaryDomains',
'dict-side': 'dictionaries',
'source-side': 'sources'
}
return mappings[inpt]
}
function createSideMenuElement(id, name, count, isSelected) {
const child = document.createElement('li')
child.id = id
const anchr = document.createElement('a')
anchr.classList = 'sel-anchr'
anchr.setAttribute('href', '#')
child.appendChild(anchr)
const divforimg = document.createElement('div')
const img = document.createElement('img')
divforimg.appendChild(img)
if (isSelected) {
img.setAttribute('src', '/images/square-checkbox-solid.svg')
} else {
img.setAttribute('src', '/images/chevron-right.svg')
}
const nameSpan = document.createElement('span')
nameSpan.innerHTML = name
nameSpan.classList = 'nav-section-content-desc'
const countSpan = document.createElement('span')
countSpan.innerHTML = count
anchr.appendChild(divforimg)
anchr.appendChild(nameSpan)
anchr.appendChild(countSpan)
return child
}
selectMoreIDs.forEach(button => {
const buttonElement = document.querySelector(`#${button}`)
buttonElement.addEventListener('click', selectMoreListener)
})
sectionIDBattery = -1
/// logic to update selectedIDs state of checked items
function checkOrUncheck(sectionId, id) {
const checked = selectedIds[sectionId].filter(e => id === e)
if (checked.length > 0) {
selectedIds[sectionId] = selectedIds[sectionId].filter(e => id !== e)
} else {
selectedIds[sectionId].push(id)
}
}
function listener(cnt) {
return e => {
const searchParams = url.searchParams
searchParams.append(
queryOrderedArr[Object.keys(queryOrderedArr)[cnt]],
e.currentTarget.parentElement.id
)
// console.log(url.searchParams.getAll('sl'))
window.location.href = url
}
}
function finishSelectingFromModal(e) {
const newValues = []
Array.from(document.querySelector('#modal-list').children).forEach(e => {
const checkField = e.children[0].children[0]
const labelId = e.children[1]
// console.log(checkField, labelId, labelId.getAttribute('for'))
if (checkField.checked) {
newValues.push(labelId.getAttribute('for'))
}
})
const url = new URL(window.location.href)
const searchParams = url.searchParams
searchParams.delete(queryOrderedArr[sectionIDBattery])
newValues.forEach(e => {
searchParams.append(queryOrderedArr[sectionIDBattery], e)
})
window.location.href = url
}
function reRenderSpecific(event) {
const idstring = event.currentTarget.id
const id = idstring.replace('show-all-', '')
// console.log(id)
rePlotAndCheck(id, [], true)
clearModalFiltersWID(id)
}
function initClickableForList(sectionId) {
let QOACounter = 0
document.querySelectorAll(`#${sectionId}`).forEach(e => {
e.querySelectorAll('a.sel-anchr').forEach(a => {
a.addEventListener('click', listener(QOACounter))
})
QOACounter++
})
}
/* function applied to reset filter(s) or select modal return state */
function rePlotAndCheck(sectionId, selectedIdsArray, plotInverse = false) {
const doc = document.querySelector(`#${sectionId}.nav-section-content`)
doc.innerHTML = ''
initializedValues[sectionId].items.forEach(el => {
const isSelected = selectedIdsArray.filter(e => e === el.id).length > 0
if (isSelected) {
doc.appendChild(
createSideMenuElement(el.id, el.name, el.count, isSelected)
)
} else if (plotInverse) {
doc.appendChild(
createSideMenuElement(el.id, el.name, el.count, isSelected)
)
}
})
if (doc.children.length < 1) {
initializedValues[sectionId].items.forEach(el => {
doc.appendChild(createSideMenuElement(el.id, el.name, el.count, false))
})
}
initClickableForList(sectionId)
const anchr = document.createElement('a')
anchr.classList = ' showall d-none'
anchr.id = `show-all-${sectionId}`
anchr.href = '#'
const img = document.createElement('img')
img.setAttribute('src', '/images/chevron-left-blue.svg')
const span = document.createElement('span')
span.innerHTML = 'PRIKAŽI VSE'
anchr.appendChild(img)
anchr.appendChild(span)
anchr.addEventListener('click', reRenderSpecific)
doc.appendChild(anchr)
if (selectedIdsArray.length > 0) {
const anchr = document.querySelector(`#show-all-${sectionId}`)
// console.log(`#show-all-${sectionId}`)
anchr.classList = 'showall'
}
}
/// TODO, some better option than global variable sectionIDBattery?
function selectFromModal(event) {
// console.log(event.target.id)
if (event.target.id) {
checkOrUncheck(sectionIDBattery, event.target.id)
}
}
function clearModalFilters() {
Array.from(document.querySelector('#modal-list').children).forEach(e => {
e.children[0].children[0].checked = false
})
selectedIds[sectionIDBattery] = []
}
function clearModalFiltersWID(id) {
Array.from(document.querySelector('#modal-list').children).forEach(e => {
e.children[0].children[0].checked = false
})
selectedIds[id] = []
}
function initializeClickables() {
let QOACounter = 0
ids.forEach(element => {
document.querySelectorAll(`#${element}`).forEach(e => {
e.querySelectorAll('a.sel-anchr').forEach(a => {
a.addEventListener('click', listener(QOACounter))
})
})
QOACounter++
})
document
.querySelector('#modal-list')
.addEventListener('click', selectFromModal)
document
.querySelector('#sf-modal-button')
.addEventListener('click', finishSelectingFromModal)
document.querySelector('#ccf').addEventListener('click', clearModalFilters)
Array.from(document.querySelectorAll('.showall')).forEach(e =>
e.addEventListener('click', event => {
sectionIDBattery = event.currentTarget.id.replace('show-all-', '')
const url = new URL(window.location.href)
const searchParams = url.searchParams
searchParams.delete(queryOrderedArr[sectionIDBattery])
window.location.href = url
})
)
document.querySelector('#clear-filters').addEventListener('click', e => {
const url = new URL(window.location.href)
const searchParams = url.searchParams
Object.keys(queryOrderedArr).forEach(queryParamKey => {
searchParams.delete(queryOrderedArr[queryParamKey])
})
window.location.href = url
})
}
// initModalList('id1', langsMapModal)
/// global run
initializeClickables()
@@ -0,0 +1,110 @@
/* global bootstrap */
let tooltipTriggerList = []
let dynamicTooltipList
function setDynamicTooltipList(value) {
dynamicTooltipList = value
}
dynamicTooltipList = []
resetTooltipTriggerList() // init
/**
* tooltipList usage doc:
* tooltipList is expected to have a list of items that will be put on to
* and HTML unordered list. The tooltip element is required to have the
* following attribute:
* data-tooltip-content -> list of items seperated with ;
*
*/
// Presumably used in other files, like consultancy.js.
// eslint-disable-next-line no-unused-vars
const tooltipListWOL = styles => {
generateTooltipWithOrderedList(tooltipTriggerList, styles)
}
const tooltipList = (content, styles) => {
generateTooltipWithStyles(tooltipTriggerList, content, styles)
}
function generateTooltipWithOrderedList(list = tooltipTriggerList, styles) {
return generateTooltipBase(list, function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl, {
html: true,
title: () => {
let content = '<ul>'
content += tooltipTriggerEl
.getAttribute('data-tooltip-content')
.split(',')
.reduce((acc, value) => {
if (value) {
return `
${acc}
<li>
${value}
</li>
`
} else {
return acc
}
}, '')
content += '</ul>'
return content
},
customClass: styles
})
})
}
function generateTooltipWithStyles(
list = tooltipTriggerList,
content = el => {
el.getAttribute('data-tooltip-content')
},
styles = 'tooltip-list-default-class'
) {
let nonEmptyContent
if (!content) {
nonEmptyContent = el => {
el.getAttribute('data-tooltip-content')
}
} else {
nonEmptyContent = content
}
return generateTooltipBase(list, function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl, {
html: true,
title: nonEmptyContent,
customClass: styles
})
})
}
function generateTooltipBase(list, fn) {
return list.map(fn)
}
function resetTooltipTriggerList() {
tooltipTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
)
}
function reinitalizeDefaultTooltipSet() {
resetTooltipTriggerList()
tooltipList()
}
function createTooltip(element, content, styles) {
return new bootstrap.Tooltip(element, {
html: true,
title: content,
customClass: styles
})
}
@@ -0,0 +1,14 @@
/* global trustedTypes, DOMPurify */
if (window.trustedTypes && trustedTypes.createPolicy) {
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => {
const trustedType = DOMPurify.sanitize(string, {
RETURN_TRUSTED_TYPE: true
})
console.log('REMOVED:', DOMPurify.removed) // eslint-disable-line
return trustedType
}
})
}
+259
View File
@@ -0,0 +1,259 @@
/* global axios */
function autoGrow(element) {
element.style.height = '5px'
element.style.height = element.scrollHeight + 'px'
}
// FIREFOX ISSUE FIX
const firefoxIssueyBoi = document.querySelector('.institution-input')
if (firefoxIssueyBoi) autoGrow(firefoxIssueyBoi)
// END FIREFOX ISSUE FIX
function autoGrowListener(e) {
autoGrow(e.target)
}
// (Lineheight) (measurement) * #lines + (padding) (measurementforpadding)
function minHeightCalcRem(
lineheightSize,
numberOfLines,
paddingTopAndBottomSize,
lineheightSizeMeasurement = 'rem',
paddingTopAndBottomSizeMeasurement = 'px'
) {
return `calc(${lineheightSize}${lineheightSizeMeasurement}*${numberOfLines} + ${paddingTopAndBottomSize}${paddingTopAndBottomSizeMeasurement})`
}
// COMMENTED CODE IS THE INITIAL STRUCTURE BEFORE PATCHING
/*
function abortEditing(
btnGrp,
oldButtons,
areaEl,
translationEl,
removeSpecifics = []
) {
btnGrp.className = 'd-none'
oldButtons.style.display = 'block'
const getRow = btnGrp.parentElement.parentElement
getRow.classList.remove('selected-row')
const getInputs = getRow.querySelectorAll('.w-50')
removeSpecifics.forEach(el => el.remove())
getInputs.forEach(el => el.parentElement.remove())
areaEl.style.display = 'table-cell'
translationEl.style.display = 'table-cell'
}
*/
function abortConsultancyUserEditing(fieldInfo) {
fieldInfo.newButtonGroup.className = 'd-none'
fieldInfo.tableButtons.style.display = 'block'
const getRow = fieldInfo.newButtonGroup.parentElement.parentElement
getRow.classList.remove('selected-row')
const getInputs = getRow.querySelectorAll('.w-50')
fieldInfo.tdName.remove()
getInputs.forEach(el => el.parentElement.remove())
fieldInfo.tDataDomain.style.display = 'table-cell'
fieldInfo.tDataName.style.display = 'table-cell'
}
/*
function saveChanges(
btnGrp,
inputDomain,
inputTranslation,
oldButtons,
tDataDomain,
tDataTranslation
) {
const inputDomainNewValue = inputDomain.value
const inputTranslationNewValue = inputTranslation.value
btnGrp.className = 'd-none'
oldButtons.style.display = 'block'
const getRow = btnGrp.parentElement.parentElement
getRow.classList.remove('selected-row')
inputDomain.parentElement.remove()
inputTranslation.parentElement.remove()
tDataDomain.style.display = 'table-cell'
tDataDomain.textContent = inputDomainNewValue
tDataTranslation.style.display = 'table-cell'
tDataTranslation.textContent = inputTranslationNewValue
}
*/
// Presumably used in other files, like consultancy.js.
// eslint-disable-next-line no-unused-vars
function inputFieldsChecker() {
const { inputDomainEl, inputTranslationEl } = window.adminElements
const addAreaButton = document.getElementById('add-area')
if (inputDomainEl.value.length && inputTranslationEl.value.length) {
addAreaButton.disabled = false
} else {
addAreaButton.disabled = true
}
}
function saveConsultantFrontend(fieldInfo) {
// Please optimize this patched code in further dev
fieldInfo.tDataDomain.innerHTML = fieldInfo.inputDomain.value
? fieldInfo.inputDomain.value
: ''
abortConsultancyUserEditing(fieldInfo)
}
function saveConsultantDomain(fieldInfo) {
const data = {
id: fieldInfo.tDataName.parentElement.id,
value: fieldInfo.inputDomain.value
}
// procedure to store DB changes
axios
.put('/api/v1/consultancy/update-consultant-domain', data)
.then(() => {
// On success, update frontend
saveConsultantFrontend(fieldInfo)
})
.catch(err => {
console.log(err)
})
}
function editRowBodyForConsultancy(fieldInfo) {
fieldInfo.getRow.className = 'selected-row'
fieldInfo.tableButtons.style.display = 'none'
fieldInfo.inputDomain.className = 'form-control w-50'
fieldInfo.inputDomain.value = fieldInfo.tDataDomainText
fieldInfo.tdName.innerHTML = fieldInfo.tDataNameText
// tDataDomain.remove()
fieldInfo.tDataDomain.style.display = 'none'
// Please optimize this patched code in further dev
if (fieldInfo.tdDomain.parentElement !== fieldInfo.getRow) {
fieldInfo.tdDomain.appendChild(fieldInfo.inputDomain)
fieldInfo.getRow.insertBefore(fieldInfo.tdDomain, fieldInfo.tDataButtons)
} else {
fieldInfo.tDataButtons.style.display = 'table-cell'
}
if (fieldInfo.tdName.parentElement !== fieldInfo.getRow) {
fieldInfo.getRow.insertBefore(fieldInfo.tdName, fieldInfo.tDataName)
} else {
fieldInfo.tDataName.style.display = 'table-cell'
}
// tDataTranslation.remove()
fieldInfo.tDataName.style.display = 'none'
const buttonsGroup = fieldInfo.getRow.querySelector('.buttons-group')
const newButtonGroup = document.createElement('div')
const cancelButton = document.createElement('button')
const saveButton = document.createElement('button')
fieldInfo.buttonsGroup = buttonsGroup
fieldInfo.newButtonGroup = newButtonGroup
fieldInfo.cancelButton = cancelButton
fieldInfo.saveButton = saveButton
cancelButton.textContent = 'Prekliči'
cancelButton.className = 'btn btn-secondary me-2'
cancelButton.style.height = '33px'
cancelButton.style.width = '105px'
cancelButton.addEventListener('click', () =>
abortConsultancyUserEditing(fieldInfo)
)
saveButton.textContent = 'POTRDI'
saveButton.className = 'btn btn-primary'
saveButton.style.height = '33px'
saveButton.style.width = '105px'
saveButton.addEventListener('click', () => saveConsultantDomain(fieldInfo))
newButtonGroup.className = 'd-flex justify-content-end me-3'
newButtonGroup.appendChild(cancelButton)
newButtonGroup.appendChild(saveButton)
buttonsGroup.insertBefore(newButtonGroup, fieldInfo.tableButtons)
}
// Presumably used in other files, like consultancy.js.
// eslint-disable-next-line no-unused-vars
function handleDomainsClickForConsultancy({ target }) {
const editRow = target.closest('.edit-row-btn')
const deleteRow = target.closest('.delete-row-btn')
const tableButtons = target.closest('.table-buttons')
if (deleteRow) {
const getRow = deleteRow.parentElement.parentElement.parentElement
const body = { data: { id: getRow.id } }
const modalUseBtn = document.getElementById('modal-use-btn')
modalUseBtn.addEventListener('click', () => {
axios
.delete('/api/v1/consultancy/delete-consultant', body)
.then(() => {
getRow.remove()
})
.catch(err => {
console.log('Cannot delete entry, reason: ' + err)
})
})
}
if (editRow) {
const getRow = editRow.parentElement.parentElement.parentElement
const tDataDomain = getRow.querySelector('.tdata-area')
const tDataName = getRow.querySelector('.tdata-name')
const tDataButtons = getRow.querySelector('.buttons-group')
const tDataDomainText = tDataDomain.textContent
const tDataNameText = tDataName.textContent
const tdDomain = document.createElement('td')
const inputDomain = document.createElement('input')
const tdName = document.createElement('td')
const fieldInfo = {
editRow,
deleteRow,
tableButtons,
getRow,
tDataDomain,
tDataName,
tDataButtons,
tDataDomainText,
tDataNameText,
tdDomain,
inputDomain,
tdName
}
editRowBodyForConsultancy(fieldInfo)
/*
console.log(`${editRow} ${deleteRow} ${tableButtons}
- ${tDataDomain}
- ${getRow} ${tDataName} ${tDataButtons}
- ${tDataDomainText} ${tDataNameText} ${tdDomain}
- ${inputDomain} ${inputDomain} ${tdName}
`)
*/
}
}
/* ========= specific pages logic ========= */
// pages/consultancy/ask.pug
try {
document.querySelectorAll('.r-3').forEach(element => {
element.style.minHeight = minHeightCalcRem(1.5, 3, 12) // calculation of 16px(1rem) for 3 lines -> 3 * 24 (line height 24px -> 1.5rem) + 12 (padding bot + top)
element.addEventListener('input', autoGrowListener)
})
const elt = document.querySelector('.institution-input')
elt.style.minHeight = minHeightCalcRem(1.5, 1, 12) // calculation of 16px(1rem) for 1 line -> 1 * 24 (line height 24px -> 1.5rem) + 12 (padding bot + top)
elt.addEventListener('input', autoGrowListener)
} catch (e) {
// other than ask.pug file is probably going to error out here
}
// end pages/consultancy/ask.pug
try {
const elt = document.querySelector('#opinion')
elt.style.minHeight = minHeightCalcRem(1.5, 4, 12) // calculation of 16px(1rem) for 1 line -> 1 * 24 (line height 24px -> 1.5rem) + 12 (padding bot + top)
} catch (e) {}