Вход Регистрация
Файл: public/assets/js/main.js
Строк: 1542
<?php
import 
* as bootstrap from 'bootstrap'
import __ from './translate.js'
import './globals.js'
import './tiptap-editor.js'
import './prettify.js'

const csrfToken document.querySelector('meta[name="csrf-token"]')?.content

function getNavbarHeight() {
    
let max 0
    document
.querySelectorAll('.app-header, .app-topnav').forEach(el => {
        
max Math.max(maxel.getBoundingClientRect().bottom)
    })
    if (!
max) {
        
document.querySelectorAll('body > *, body > * > *').forEach(el => {
            if (
window.getComputedStyle(el).position === 'fixed') {
                const 
rect el.getBoundingClientRect()
                if (
rect.top >= && rect.top && rect.bottom 0
                    
&& rect.bottom window.innerHeight 0.5
                    
&& rect.width window.innerWidth 0.5) {
                    
max Math.max(maxrect.bottom)
                }
            }
        })
    }
    return 
max
}

function 
initShortView(container document) {
    
container.querySelectorAll('.section-content.short-view:not(.clamped):not(.expanded)').forEach(function (el) {
        const 
hiddenPixels el.scrollHeight el.clientHeight
        
if (hiddenPixels 100) {
            
el.classList.add('clamped')
            const 
btn document.createElement('button')
            
btn.type 'button'
            
btn.className 'btn btn-sm btn-adaptive mt-2'
            
btn.textContent 'Показать полностью'
            
btn.addEventListener('click', function () {
                
el.classList.add('expanded')
                
el.classList.remove('clamped')
                
btn.remove()
            })
            
el.after(btn)
        } else if (
hiddenPixels 0) {
            
el.classList.remove('short-view')
        }
    })
}

function 
ajax({ urltype 'GET'data nulldataType 'json'beforeSendcompletesuccesserror }) {
    if (
beforeSendbeforeSend()

    const 
options = {
        
methodtype.toUpperCase(),
        
headers: {
            
'X-CSRF-TOKEN'csrfToken,
            
'X-Requested-With''XMLHttpRequest',
        }
    }

    if (
data) {
        if (
data instanceof FormData) {
            
options.body data
        
} else {
            
options.headers['Content-Type'] = 'application/x-www-form-urlencoded'
            
options.body = new URLSearchParams(data)
        }
    }

    
fetch(urloptions)
        .
then(res => dataType === 'json' res.json() : res.text())
        .
then(responseData => { if (successsuccess(responseData) })
        .catch(
err => { if (errorerror(nullerr.messageerr) })
        .finally(() => { if (
completecomplete() })
}

function 
applyMask(elmask) {
    
el.addEventListener('input', function () {
        const 
digits el.value.replace(/D/g'')
        
let result ''
        
let di 0
        
for (let i 0mask.length && di digits.lengthi++) {
            
result += mask[i] === '0' digits[di++] : mask[i]
        }
        
el.value result
    
})
}

document.addEventListener('DOMContentLoaded', function () {
    
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => new bootstrap.Tooltip(el))

    const 
popovers document.querySelectorAll('[data-bs-toggle="popover"]')
    
popovers.forEach(el => new bootstrap.Popover(el))

    
document.body.addEventListener('click', function (e) {
        if (!
e.target.closest('[data-bs-toggle="popover"]') && !e.target.closest('.popover')) {
            
popovers.forEach(el => bootstrap.Popover.getInstance(el)?.hide())
        }
    })

    const 
colorpicker document.querySelector('.colorpicker')
    const 
colorpickerAddon document.querySelector('.colorpicker-addon')
    if (
colorpicker && colorpickerAddon) {
        
colorpicker.addEventListener('input', () => colorpickerAddon.value colorpicker.value)
        
colorpickerAddon.addEventListener('input', () => colorpicker.value colorpickerAddon.value)
    }

    
document.querySelectorAll('.phone').forEach(el => applyMask(el'+0 000 000-00-00-00'))
    
document.querySelectorAll('.birthday').forEach(el => applyMask(el'00.00.0000'))

    const 
scrollupBtn document.querySelector('.scrollup')
    if (
scrollupBtn) {
        
window.addEventListener('scroll', function () {
            const 
visible window.scrollY 200
            scrollupBtn
.style.opacity visible '1' '0'
            
scrollupBtn.style.pointerEvents visible 'auto' 'none'
        
})
        
scrollupBtn.addEventListener('click', function () {
            
window.scrollTo({ top0behavior'smooth' })
            return 
false
        
})
    }

    
document.querySelector('.js-messages-block')?.addEventListener('show.bs.dropdown', function () {
        
getNewMessages()
    })

    function 
setTheme(theme) {
        
document.documentElement.setAttribute('data-bs-theme'theme)
        const 
icon theme === 'dark' 'fa-moon' 'fa-sun'
        
const themeIcon document.getElementById('theme-icon-active')
        if (
themeIconthemeIcon.className = `fa-regular ${icon} fa-lg`
        
ajax({ type'POST'url'/ajax/set-theme'data: { theme } })
    }

    
document.querySelectorAll('[data-bs-theme-value]').forEach(el => {
        
el.addEventListener('click', () => setTheme(el.dataset.bsThemeValue))
    })

    if (
window.location.hash) {
        const 
initialHash window.location.hash
        
if (initialHash === '#comments') {
            
history.replaceState(null''location.pathname location.search)
        }
        
setTimeout(function () {
            const 
target document.querySelector(initialHash)
            if (
target) {
                const 
navbarHeight getNavbarHeight()
                
window.scrollTo({ toptarget.getBoundingClientRect().top window.scrollY navbarHeightbehavior'instant' })
            }
        }, 
100)
    } else if (new 
URLSearchParams(location.search).has('page')) {
        const 
commentsEl document.querySelector('#comments')
        if (
commentsEl) {
            
setTimeout(function () {
                const 
navbarHeight getNavbarHeight()
                
window.scrollTo({ topcommentsEl.getBoundingClientRect().top window.scrollY navbarHeightbehavior'instant' })
            }, 
100)
        }
    }

    
setTimeout(initShortView300)

    
prettyPrint()

    
tags.init('.input-tag', {
        
allowNewtrue,
        
server'/blogs/tags-search',
        
liveServertrue,
        
clearEndtrue,
        
allowCleartrue,
        
suggestionsThreshold2,
        
max10,
        
separator: [','],
        
addOnBlurtrue,
    })

    
fancybox.bind('[data-fancybox]:not(.fancybox-exclude)', {})

    
let hoveredCarousel null
    document
.querySelectorAll('.f-carousel').forEach(el => {
        const 
carousel fancyCarousel(el, { infinitetrueadaptiveHeighttrue }, fancyCarouselPlugins)
        
carousel.init()

        
el.addEventListener('mouseenter', () => hoveredCarousel carousel)
        
el.addEventListener('mouseleave', () => { if (hoveredCarousel === carouselhoveredCarousel null })
    })

    
document.addEventListener('keydown'=> {
        if (!
hoveredCarousel) return
        if (
e.key === 'ArrowLeft') {
            
e.preventDefault()
            
hoveredCarousel.prev()
        } else if (
e.key === 'ArrowRight') {
            
e.preventDefault()
            
hoveredCarousel.next()
        }
    })

    
document.querySelectorAll('.slide-thumb-link[data-type="html5video"]').forEach(link => {
        const 
video link.querySelector('video')
        if (!
video) return

        const 
setThumb = () => {
            const 
canvas document.createElement('canvas')
            
canvas.width video.videoWidth || 160
            canvas
.height video.videoHeight || 90
            canvas
.getContext('2d').drawImage(video00canvas.widthcanvas.height)
            
link.dataset.thumb canvas.toDataURL('image/jpeg'0.8)
        }

        
video.readyState >= setThumb() : video.addEventListener('loadeddata'setThumb, { oncetrue })
    })
})

/* Показ формы загрузки файла */
window.showAttachForm = function () {
    const 
btn document.querySelector('.js-attach-button')
    const 
form document.querySelector('.js-attach-form')
    if (
btnbtn.style.display 'none'
    
if (formform.style.display 'block'
    
return false
}

/* Переход к форме ввода */
window.postJump = function () {
    const 
form document.querySelector('.section-form')
    if (
form) {
        const 
navbarHeight getNavbarHeight()
        
window.scrollTo({ topform.getBoundingClientRect().top window.scrollY navbarHeightbehavior'smooth' })
    }
}

/* Сворачивание/разворачивание */
window.toggleComment = function (id) {
    const 
body document.getElementById('comment-body-' id)
    const 
expandLabel document.getElementById('comment-expand-' id)
    const 
ctrl document.getElementById('comment-ctrl-' id)
    if (!
body) return

    const 
isHidden body.classList.toggle('d-none')

    if (
expandLabelexpandLabel.classList.toggle('d-none', !isHidden)

    if (
ctrl) {
        const 
icon ctrl.querySelector('i')
        if (
iconicon.className isHidden 'fa fa-plus' 'fa fa-minus'
        
const line ctrl.querySelector('.comment-thread-line')
        if (
lineline.classList.toggle('d-none'isHidden)
    }
}

/* Открыть форму ответа под комментарием */
window.openReplyForm = function (idcallback) {
    
document.querySelectorAll('.reply-form').forEach(function (f) {
        
f.classList.add('d-none')
    })
    const 
form document.getElementById('reply-form-' id)
    if (!
form) return false

    form
.classList.remove('d-none')

    const 
textarea form.querySelector('textarea')
    const 
editorId textarea?.id

    
if (textarea && editorId && !window._tiptapEditors?.[editorId]) {
        
textarea.classList.add('tiptap')
        
import('./tiptap.js').then(({ initEditors }) => {
            
initEditors([textarea])
            const 
ed window._tiptapEditors?.[editorId]
            
ed?.commands.focus()
            
callback?.(ed)
        })
    } else {
        const 
ed window._tiptapEditors?.[editorId]
        
ed?.commands.focus()
        
callback?.(ed)
    }

    return 
false
}

/* Закрыть форму ответа */
window.closeReplyForm = function (id) {
    
document.getElementById('reply-form-' id)?.classList.add('d-none')
}

/* Тогл панели форматирования в форме ответа */
window.toggleReplyToolbar = function (btn) {
    
btn.closest('.reply-form').classList.toggle('toolbar-visible')
}

/* AJAX отправка формы ответа на комментарий */
document.addEventListener('submit', function (e) {
    const 
form e.target.closest('.reply-form form')
    if (!
form) return

    
e.preventDefault()

    const 
errorEl form.querySelector('.reply-error')
    if (
errorElerrorEl.textContent ''

    
const submitBtn form.querySelector('button[type="submit"], button:not([type="button"])')
    if (
submitBtnsubmitBtn.disabled true

    fetch
(form.action, {
        
method'POST',
        
headers: { 'Accept''application/json''X-Requested-With''XMLHttpRequest' },
        
body: new FormData(form),
    })
        .
then(function (r) { return r.json().then(function (d) { return { okr.okdata} }) })
        .
then(function ({ okdata }) {
            if (
ok) {
                const 
hash data.redirect.includes('#') ? data.redirect.split('#')[1] : ''
                
window.location.hash hash
                window
.location.reload()
            } else {
                const 
msg Object.values(data.errors || {}).flat().join(', ')
                if (
errorElerrorEl.textContent msg
                
if (submitBtnsubmitBtn.disabled false
            
}
        })
        .catch(function () {
            if (
submitBtnsubmitBtn.disabled false
        
})
})

/* Схлопывание/разворачивание ветки комментариев */
document.addEventListener('click', function (e) {
    const 
btn e.target.closest('.comment-collapse-btn')
    if (!
btn) return
    const 
id btn.dataset.id
    
const children document.getElementById('comment-children-' id)
    if (!
children) return
    const 
icon btn.querySelector('i')
    const 
collapsed children.classList.toggle('d-none')
    
icon.className collapsed 'fa fa-plus text-muted' 'fa fa-minus text-muted'
})

/* Переключение языка (ajax, без перезагрузки на /language) */
document.addEventListener('click', function (e) {
    const 
el e.target.closest('[data-lang]')
    if (!
el) return
    
e.preventDefault()
    
ajax({
        
url'/language/' el.dataset.lang,
        
type'POST',
        
success: () => location.reload(),
    })
})

/* Ответ на сообщение (для форумов/стен без вложенных комментариев) */
window.postReply = function (el) {
    
postJump()

    const 
authorEl el.closest('.section')?.querySelector('.section-author')
    const 
author authorEl?.dataset.login || authorEl?.textContent.trim()
    if (!
author) return false

    
const editor window._tiptapActiveEditor
    
if (!editor) return false

    
if (authorEl.matches('a')) {
        
editor.chain().focus('end', { scrollIntoViewfalse }).insertContent([
            { 
type'mention'attrs: { idauthorlabelauthor } },
            { 
type'text'text' ' },
        ]).
run()
    } else {
        
editor.chain().focus('end', { scrollIntoViewfalse }).insertContent({ type'text'textauthor ', ' }).run()
    }

    return 
false
}

/* Цитирование сообщения */
function doInsertQuote (editorauthorElauthordatemessage) {
    if (!
message) {
        if (
author) {
            
editor.chain().focus('end', { scrollIntoViewfalse }).insertContent([
                { 
type'mention'attrs: { idauthorlabelauthor } },
                { 
type'text'text' ' },
            ]).
run()
        }
        return
    }

    const 
quoteContent = [
        {
            
type'blockquote',
            
attrs: { authorauthor ? (authorEl.matches('a') ? '@' '') + author + (date ' ' date '') : (date || null) },
            
content: [{ type'paragraph'content: [{ type'text'textmessage }] }],
        },
        { 
type'paragraph' },
    ]

    if (
editor.isEmpty) {
        
editor.chain().focus('end', { scrollIntoViewfalse }).setContent({ type'doc'contentquoteContent }).run()
    } else {
        const 
doc editor.state.doc
        
const lastChild doc.lastChild
        
const insertPos = (lastChild && lastChild.type.name === 'paragraph' && lastChild.childCount === 0)
            ? 
doc.content.size lastChild.nodeSize
            
doc.content.size
        editor
.chain().focus('end', { scrollIntoViewfalse }).insertContentAt(insertPosquoteContent).run()
    }
}

window.postQuote = function (el) {
    const 
commentItem el.closest('.comment-item')

    if (
commentItem) {
        const 
id       commentItem.dataset.id
        
const rcRight  el.closest('.comment-right')
        const 
authorEl rcRight?.querySelector('.section-author')
        const 
author   authorEl?.dataset.login || authorEl?.textContent.trim() || null
        
const dateEl   rcRight?.querySelector('.section-date')
        const 
date     = (dateEl?.dataset.date || dateEl?.textContent || '').trim()
        const clone    = 
rcRight?.querySelector('.section-message')?.cloneNode(true)
        clone?.
querySelectorAll('blockquote').forEach(bq => bq.remove())
        const 
message  = clone?.textContent.trim() || ''

        
openReplyForm(id, function (editor) {
            if (
editordoInsertQuote(editorauthorElauthordatemessage)
        })
        return 
false
    
}

    
postJump()

    const 
post     el.closest('.section')
    const 
authorEl post?.querySelector('.section-author')
    const 
author   authorEl?.dataset.login || authorEl?.textContent.trim() || null
    
const dateEl   post?.querySelector('.section-date')
    const 
date     = (dateEl?.dataset.date || dateEl?.textContent || '').trim()
    const clone    = 
post?.querySelector('.section-message')?.cloneNode(true)
    const 
editor   window._tiptapActiveEditor

    
if (!editor) return false

    
clone?.querySelectorAll('blockquote').forEach(bq => bq.remove())
    const 
message = clone?.textContent.trim() || ''

    
doInsertQuote(editorauthorElauthordatemessage)
    return 
false
}

/* Подтверждение действия */
const confirmedElements = new WeakSet()

window.confirmAction = function (el) {
    const 
message el.dataset.confirm || 'Вы уверены?'

    
if (confirmedElements.has(el)) {
        
confirmedElements.delete(el)
        return 
true
    
}

    
confirm(message, function (result) {
        if (!
result) return
        const 
form el.matches('form') ? el el.closest('form')
        if (
form) {
            
confirmedElements.add(form)
            
form.submit()
        } else {
            const 
href el.getAttribute('href')
            if (
hrefwindow.location.href href
        
}
    })

    return 
false
}

/* Отправка жалобы на спам */
window.sendComplaint = function (el) {
    
confirm(__('confirm_complain_submit'), function (result) {
        if (!
result) return

        
ajax({
            
data: { idel.dataset.idtypeel.dataset.typepageel.dataset.page },
            
dataType'json'type'post'url'/ajax/complaint',
            
success: function (data) {
                if (!
data.success) { notyf.error(data.message); return }

                
el.classList.add('text-muted''pe-none')
                
el.querySelector('i').className 'fa fa-check'
                
notyf.success(__('complain_submitted'))
            }
        })
    })

    return 
false
}

/* Добавление или удаление закладок */
window.bookmark = function (el) {
    
ajax({
        
data: { tidel.dataset.tid },
        
dataType'json'type'post'url'/forums/bookmarks/perform',
        
success: function (data) {
            if (!
data.success) { notyf.error(data.message); return }

            
notyf.success(data.message)
            
el.textContent data.type === 'added' el.dataset.from el.dataset.to
        
}
    })

    return 
false
}

/* Удаление записей */
window.deletePost = function (el) {
    
confirm(__('confirm_message_delete'), function (result) {
        if (!
result) return

        
ajax({
            
urlel.getAttribute('href'), type'delete'dataType'json',
            
success: function (data) {
                if (
data.success) {
                    
notyf.success(data.message)
                    
el.closest('.section').style.display 'none'
                
} else {
                    
notyf.error(data.message)
                }
            }
        })
    })

    return 
false
}

/* Редактирование комментария в модальном окне */
window.openEditModal = function (el) {
    const 
id      el.dataset.id
    
const baseUrl el.dataset.url
    
const modalEl document.getElementById('editCommentModal')

    
document.getElementById('edit-comment-id').value id
    modalEl
.dataset.editUrl baseUrl '/' id

    modalEl
.querySelector('input[type="file"]')?.setAttribute('data-id'id)
    const 
msgEl document.getElementById('edit-comment-msg')
    if (
msgElmsgEl.dataset.relateId id

    
const filesContainer modalEl.querySelector('.js-files')
    if (
filesContainerfilesContainer.innerHTML ''

    
const dataPromise fetch(baseUrl '/' id, {
        
headers: { 'X-Requested-With''XMLHttpRequest''X-CSRF-TOKEN'csrfToken }
    }).
then(=> r.json()).then(data => {
        const 
scope modalEl.querySelector('form')
        
data.files?.forEach(file => {
            const 
templateEl scope?.querySelector(file.isImage '.js-image-template' '.js-file-template')
            const 
template templateEl?.cloneNode(true)
            if (!
template) return
            if (
file.isImage) {
                
template.querySelector('img')?.setAttribute('src'file.path)
            } else {
                const 
link template.querySelector('.js-file-link')
                if (
link) { link.href file.pathlink.textContent file.name }
                const 
sizeEl template.querySelector('.js-file-size')
                if (
sizeElsizeEl.textContent file.size
            
}
            
template.querySelector('.js-file-delete')?.setAttribute('data-id'file.id)
            
filesContainer?.insertAdjacentHTML('beforeend'template.innerHTML)
        })
        return 
data.text || ''
    
})

    const 
onShown async () => {
        
modalEl.removeEventListener('shown.bs.modal'onShown)

        const 
text await dataPromise

        
if (!window._tiptapEditors?.['edit-comment-msg'] && msgEl) {
            
msgEl.classList.add('tiptap')
            const { 
initEditors } = await import('./tiptap.js')
            
initEditors([msgEl])
            
await new Promise(resolve => requestAnimationFrame(resolve))
        }

        const 
editor window._tiptapEditors?.['edit-comment-msg']
        if (
editor) {
            
editor.commands.setContent(texttrue)
            
editor.resetChanged()
        }
    }

    
modalEl.addEventListener('shown.bs.modal'onShown)
    
bootstrap.Modal.getOrCreateInstance(modalEl).show()

    return 
false
}

document.getElementById('editCommentModal')?.addEventListener('hide.bs.modal', function (e) {
    const 
editor window._tiptapEditors?.['edit-comment-msg']
    if (
editor?.getIsChanged()) {
        
e.preventDefault()
        if (
window.confirm(__('confirm_discard_changes'))) {
            
editor.resetChanged()
            
bootstrap.Modal.getInstance(this)?.hide()
        }
    }
})

document.getElementById('editCommentForm')?.addEventListener('submit', function (e) {
    
e.preventDefault()
    const 
modalEl document.getElementById('editCommentModal')
    const 
id      document.getElementById('edit-comment-id').value
    
const msg     document.getElementById('edit-comment-msg').value
    
const url     modalEl?.dataset.editUrl

    ajax
({
        
data: { msg },
        
dataType'json'type'patch'url,
        
success: function (data) {
            if (
data.success) {
                
bootstrap.Modal.getInstance(modalEl)?.hide()
                
window.location.hash '#comment_' id
                window
.location.reload()
            } else {
                
notyf.error(data.message)
            }
        }
    })
})

/* Удаление комментариев */
window.deleteComment = function (el) {
    const 
item el.closest('.comment-item, .section')

    
confirm(__('confirm_message_delete'), function (result) {
        if (!
result) return

        
ajax({
            
dataType'json'type'delete'url'/comments/' el.dataset.id,
            
success: function (data) {
                if (
data.success) {
                    
notyf.success(__('message_deleted'))
                    if (
item) {
                        const 
content item.querySelector('.comment-content')
                        if (
contentcontent.innerHTML '<div class="comment-removed text-muted fst-italic small mb-2">' __('comment_removed') + '</div>'
                    
}
                } else {
                    
notyf.error(data.message)
                }
            }
        })
    })

    return 
false
}

/* Изменение рейтинга */
window.changeRating = function (el) {
    
ajax({
        
data: { idel.dataset.idtypeel.dataset.typevoteel.dataset.vote },
        
dataType'json'type'post'url'/ajax/rating',
        
success: function (data) {
            if (
data.success) {
                const 
ratingBlock el.closest('.js-rating')
                
ratingBlock?.querySelectorAll('a').forEach(=> a.classList.remove('active'))
                if (!
data.cancelel.classList.add('active')
                const 
rating ratingBlock?.querySelector('.rating-value')
                if (
ratingrating.innerHTML data.rating
            
} else if (data.message) {
                
notyf.error(data.message)
            }
        }
    })

    return 
false
}

/* Удаляет запись из истории рейтинга */
window.deleteRating = function (el) {
    
confirm(__('confirm_message_delete'), function (result) {
        if (!
result) return

        
ajax({
            
data: { idel.dataset.id },
            
dataType'json'type'post'url'/ratings/delete',
            
success: (data) => {
                if (
data.success) {
                    
notyf.success(__('record_deleted'))
                    
el.closest('.section').style.display 'none'
                
} else {
                    
notyf.error(data.message)
                }
            }
        })
    })

    return 
false
}

/* Удаляет запись из списка жалоб */
window.deleteSpam = function (el) {
    
ajax({
        
data: { idel.dataset.id },
        
dataType'json'type'post'url'/admin/spam/delete',
        
success: function (data) {
            if (
data.success) {
                
notyf.success(__('record_deleted'))
                
el.closest('.section').style.display 'none'
            
} else {
                
notyf.error(data.message)
            }
        }
    })

    return 
false
}

/* Удаляет запись со стены сообщений */
window.deleteWall = function (el) {
    
confirm(__('confirm_message_delete'), function (result) {
        if (!
result) return

        
ajax({
            
data: { idel.dataset.idloginel.dataset.login },
            
dataType'json'type'post'url'/walls/' el.dataset.login '/delete',
            
success: function (data) {
                if (
data.success) {
                    
notyf.success(__('record_deleted'))
                    
el.closest('.section').style.display 'none'
                
} else {
                    
notyf.error(data.message)
                }
            }
        })
    })

    return 
false
}

/* Копирует текст в буфер обмена */
window.copyToClipboard = function (el) {
    const 
container el.closest('.input-group') ?? el.parentElement
    
const field container?.querySelector('input, textarea')
    const 
text el.dataset.copy ?? field?.value ?? ''

    
const fallback = () => {
        if (
field) { field.select(); document.execCommand('copy'); return }
        if (!
text) return

        const 
ta document.createElement('textarea')
        
ta.value text
        ta
.style.position 'fixed'
        
ta.style.opacity '0'
        
document.body.appendChild(ta)
        
ta.select()
        try { 
document.execCommand('copy') } catch (e) {}
        
document.body.removeChild(ta)
    }

    if (
navigator.clipboard?.writeText && text) {
        
navigator.clipboard.writeText(text).catch(fallback)
    } else {
        
fallback()
    }

    
// Галочка на иконке триггера
    
const icon el.querySelector('i')
    if (
icon && !icon.dataset.copyReset) {
        const 
prev icon.className
        icon
.dataset.copyReset '1'
        
icon.className 'fas fa-check'
        
setTimeout(() => {
            
icon.className prev
            delete icon
.dataset.copyReset
        
}, 1500)
    }

    
// Подсказка «скопировано»: на .input-group-text либо на самом триггере
    
const tooltipEl container?.querySelector('.input-group-text')
        ?? (
el.matches('[data-bs-toggle="tooltip"]') ? el null)

    if (
tooltipEl) {
        const 
original tooltipEl.getAttribute('data-bs-original-title') ?? tooltipEl.getAttribute('title')
        const 
tip bootstrap.Tooltip.getOrCreateInstance(tooltipEl)
        
tooltipEl.setAttribute('data-bs-original-title'__('copied'))
        
tip.update()
        
tip.show()

        if (
original !== null) {
            
setTimeout(() => {
                
tooltipEl.setAttribute('data-bs-original-title'original)
                
tip.update()
            }, 
1500)
        }
    }

    return 
false
}

/* Загрузка файла */
window.submitFile = function (el) {
    const 
form = new FormData()
    
form.append('file'el.files[0])
    
form.append('id'el.dataset.id)
    
form.append('type'el.dataset.type)

    const 
scope el.closest('form') ?? document
    
const filesContainer scope.querySelector('.js-files')

    
ajax({
        
dataformtype'post'dataType'json'url'/ajax/file/upload',
        
beforeSend: () => filesContainer?.insertAdjacentHTML('beforeend''<i class="fas fa-spinner fa-spin fa-3x mx-3"></i>'),
        
complete: () => filesContainer?.querySelectorAll('.fa-spinner').forEach(=> s.remove()),
        
success: function (data) {
            if (!
data.success) { notyf.error(data.message); return }

            const 
isMedia data.type === 'image' || data.type === 'video'
            
const templateEl scope.querySelector(isMedia '.js-image-template' '.js-file-template')
            const 
template templateEl?.cloneNode(true)

            if (
data.type === 'image') {
                
template?.querySelector('img')?.setAttribute('src'data.path)
            } else if (
data.type === 'video') {
                const 
img template?.querySelector('img')
                if (
img) {
                    const 
wrap img.parentElement
                    
const video document.createElement('video')
                    
video.src data.path
                    video
.className img.className
                    video
.preload 'metadata'
                    
img.replaceWith(video)
                    
wrap?.insertAdjacentHTML('beforeend''<span class="slide-play-icon">▶</span>')
                }
            } else {
                const 
link template?.querySelector('.js-file-link')
                if (
link) { link.href data.pathlink.textContent data.name }
                const 
sizeEl template?.querySelector('.js-file-size')
                if (
sizeElsizeEl.textContent data.size
            
}

            
template?.querySelector('a')?.setAttribute('data-id'data.id)
            if (
templatefilesContainer?.insertAdjacentHTML('beforeend'template.innerHTML)
        },
        
error: (_textStatus) => notyf.error('Ошибка загрузки файла: ' textStatus)
    })

    
el.value ''
    
return false
}

/* Удаление медиафайла (изображения или видео) из редактора */
window.cutMedia = function (path) {
    if (!
path) return

    const 
editor window._tiptapActiveEditor
    
if (!editor) return

    const 
normalize = (src) => { try { return new URL(src).pathname } catch { return src } }
    const 
normalizedPath normalize(path)

    const { 
statedispatch } = editor.view
    
const tr state.tr
    
const positions = []

    
state.doc.descendants(function (nodepos) {
        const 
src node.attrs.src ?? node.attrs.href
        
if (['image''video'].includes(node.type.name) && normalize(src) === normalizedPath) {
            
positions.push({ possizenode.nodeSize })
        }
    })

    
positions.reverse().forEach(({ possize }) => tr.delete(pospos size))

    
// noinspection JSUnresolvedReference
    
if (tr.docChangeddispatch(tr)
}

/* Удаление файла */
window.deleteFile = function (el) {
    
confirm(__('confirm_file_delete'), function (result) {
        if (!
result) return

        
ajax({
            
url'/ajax/file/delete'type'POST'dataType'json',
            
data: { idel.dataset.idtypeel.dataset.type },
            
success: function (data) {
                if (!
data.success) { notyf.error(data.message); return }
                if (
data.pathcutMedia(data.path)
                
el.closest('.js-file').style.display 'none'
            
},
            
error: (_textStatus) => notyf.error('Ошибка удаления файла: ' textStatus)
        })
    })

    return 
false
}

/* Показывает форму для повторной отправки кода подтверждения */
window.resendingCode = function () {
    const 
link document.querySelector('.js-resending-link')
    const 
form document.querySelector('.js-resending-form')
    if (
linklink.style.display 'none'
    
if (formform.style.display 'block'
    
return false
}

/* Показывает панель с запросами */
window.showQueries = function () {
    const 
el document.querySelector('.js-queries')
    if (!
el) return
    
el.style.display getComputedStyle(el).display === 'none' '' 'none'
}

/* Update message count */
window.updateMessageCount = function (newCount) {
    const 
data JSON.parse(localStorage.getItem('messageData') || '{}')
    
data.countMessages parseInt(newCount) || 0
    localStorage
.setItem('messageData'JSON.stringify(data))
    
localStorage.setItem('messageCount'newCount)
    
window.dispatchEvent(new Event('storage'))
}

/* Get new messages */
window.getNewMessages = function () {
    const 
notifyItem document.querySelector('.js-messages-block .app-nav__item')
    const 
badge notifyItem?.querySelector('.badge')
    const 
titleSpan document.querySelector('.app-notification__title span')
    const 
messagesList document.querySelector('.js-messages-block .js-messages')

    
ajax({
        
dataType'json'type'GET'url'/messages/new',
        
beforeSend: () => messagesList?.insertAdjacentHTML('beforeend''<li class="js-message-spin text-center"><i class="fas fa-spinner fa-spin fa-2x my-2"></i></li>'),
        
complete: () => messagesList?.querySelectorAll('.js-message-spin').forEach(=> s.remove()),
        
success(data) {
            if (!
data?.success) {
                
badge?.remove()
                if (
titleSpantitleSpan.textContent 0
                
return
            }

            const 
count data.countMessages

            
if (badge) {
                
badge.textContent count
            
} else if (notifyItem) {
                const 
newBadge document.createElement('span')
                
newBadge.className 'badge bg-notify'
                
newBadge.textContent count
                notifyItem
.append(newBadge)
            }

            
updateMessageCount(count)

            if (
titleSpantitleSpan.textContent count
            
if (messagesList) {
                
messagesList.innerHTML ''
                
messagesList.insertAdjacentHTML('beforeend'data.dialogues)
            }
        }
    })

    return 
false
}

/* Инициализирует главное изображение слайдера */
window.initSlideMainImage = function (el) {
    const 
mainHref el.getAttribute('href')
    const 
slider el.closest('.media-file')

    
slider?.querySelectorAll('.slide-thumb-link').forEach(=> l.classList.remove('fancybox-exclude'))
    
slider?.querySelectorAll(`.slide-thumb-link[href="${mainHref}"]`).forEach(=> l.classList.add('fancybox-exclude'))
}

/* Инициализирует миниатюру слайдера */
window.initSlideThumbImage = function (el) {
    const 
href el.getAttribute('href')
    const 
isVideo el.dataset.type === 'html5video'
    
const fancyboxGroup el.dataset.fancybox ?? ''
    
const slider el.closest('.media-file')
    const 
mainInner slider?.querySelector('.slide-main-inner')

    if (!
mainInner) return false

    
if (el.querySelector('.slide-thumb-image, .slide-thumb-video')?.classList.contains('active')) return false

    mainInner
.querySelector('video')?.pause()

    if (
isVideo) {
        
mainInner.innerHTML = `<video src="${href}" class="img-fluid rounded" controls preload="metadata"></video>`
    } else {
        const 
alt = (el.querySelector('img')?.getAttribute('alt') ?? '').replace(/"/g, '&quot;')
        mainInner.innerHTML =
            `<a href="
${href}" class="slide-main-link" data-fancybox="${fancyboxGroup}" onclick="return initSlideMainImage(this)">` +
            `<img src="
${href}" alt="${alt}" class="img-fluid rounded slide-main-img">` +
            `</a>`
    }

    slider?.querySelectorAll('.slide-thumb-image').forEach(img => img.classList.remove('active'))
    slider?.querySelectorAll('.slide-thumb-video').forEach(v => v.classList.remove('active'))

    const thumb = el.querySelector('.slide-thumb-image, .slide-thumb-video')
    thumb?.classList.add('active')

    return false
}

let checkTimeout
/* Проверка логина */
window.checkLogin = function (el) {
    const block = el.closest('.mb-3')
    const message = block?.querySelector('.invalid-feedback')
    const login = el.value.trim()

    if (login.length < 3) {
        block?.classList.remove('is-valid', 'is-invalid')
        if (message) message.textContent = ''
        return
    }

    clearTimeout(checkTimeout)

    checkTimeout = setTimeout(function () {
        ajax({
            url: '/check-login', type: 'POST', dataType: 'json',
            data: { login },
            success: (data) => {
                block?.classList.toggle('is-valid', data.success)
                block?.classList.toggle('is-invalid', !data.success)
                if (message) message.textContent = data.success ? '' : data.message
            },
            error: () => {
                block?.classList.remove('is-valid')
                block?.classList.add('is-invalid')
            }
        })
    }, 1000)

    return false
}

const confirmDialogEl = document.createElement('dialog')
confirmDialogEl.className = 'confirm-dialog'
confirmDialogEl.innerHTML = `
<p class="
confirm-message"></p>
<div class="
confirm-footer">
    <button type="
button" class="btn btn-secondary btn-sm js-confirm-cancel"></button>
    <button type="
button" class="btn btn-primary btn-sm js-confirm-ok"></button>
</div>`
document.body.appendChild(confirmDialogEl)

function confirm(message, callback) {
    confirmDialogEl.querySelector('.confirm-message').textContent = message
    confirmDialogEl.querySelector('.js-confirm-ok').textContent = __('buttons.ok')
    confirmDialogEl.querySelector('.js-confirm-cancel').textContent = __('buttons.cancel')
    confirmDialogEl.querySelector('.js-confirm-ok').onclick = () => { confirmDialogEl.close(); callback(true) }
    confirmDialogEl.querySelector('.js-confirm-cancel').onclick = () => { confirmDialogEl.close(); callback(false) }
    confirmDialogEl.showModal()
}

// Кнопка "
Загрузить ещё" для ленты
const feedContainer = document.getElementById('feed-container')
const feedSentinel  = document.getElementById('feed-sentinel')

if (feedContainer && feedSentinel) {
    let loading = false

    const getNextUrl = () => {
        const items = feedContainer.querySelectorAll('.feed-pagination')
        return items[items.length - 1]?.dataset.next || ''
    }

    const loader = document.createElement('div')
    loader.className = 'feed-loader d-none'
    loader.innerHTML = '<span></span><span></span><span></span><span></span><span></span>'
    feedSentinel.before(loader)

    const createLoadMoreButton = () => {
        const btn = document.createElement('button')
        btn.type = 'button'
        btn.className = 'btn btn-primary d-block mx-auto my-3'
        btn.textContent = __('buttons.load_more')
        btn.addEventListener('click', async () => {
            const nextUrl = getNextUrl()
            if (!nextUrl || loading) return

            loading = true
            btn.remove()
            loader.classList.remove('d-none')

            try {
                const response = await fetch(nextUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
                const html     = await response.text()
                const temp     = document.createElement('div')
                temp.innerHTML = html

                if (temp.querySelector('.feed-pagination')?.dataset.empty === '1') return

                feedContainer.append(...temp.children)
                setTimeout(initShortView, 100)

                if (getNextUrl()) loader.before(createLoadMoreButton())
            } finally {
                loading = false
                loader.classList.add('d-none')
            }
        })
        return btn
    }

    if (getNextUrl()) loader.before(createLoadMoreButton())
}
?>
Онлайн: 0
Реклама