Files
comment/js/comment.js

1061 lines
48 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ====================================================================
ОБЕРТКА ДЛЯ ОЖИДАНИЯ JQUERY
==================================================================== */
(function waitForJQuery() {
if (typeof jQuery === 'undefined') {
setTimeout(waitForJQuery, 10);
} else {
(function($) {
if (typeof aveabspath === 'undefined') {
window.aveabspath = '/';
}
function initCommentTimers() {
$('.timer-count:not(.timer-running)').each(function() {
var $timer = $(this);
var timeLeft = parseInt($timer.attr('data-left'));
// Если время не определено или уже вышло — помечаем как запущенный и пропускаем
if (isNaN(timeLeft) || timeLeft <= 0) {
$timer.addClass('timer-running');
return;
}
$timer.addClass('timer-running');
var cid = $timer.attr('id').replace('timer_', '');
var countdown = setInterval(function() {
timeLeft--;
if (timeLeft <= 0) {
clearInterval(countdown);
// --- Проверка режима редактирования ---
var isEditing = $('#comment_wrapper_' + cid).find('.edit-form-container').length > 0;
if (!isEditing) {
// Находим кнопки управления
var $controls = $('#controls_' + cid);
if ($controls.length) {
$controls.fadeOut(300, function() { $(this).remove(); });
}
// Обновляем статус визуально
$('#timer_container_' + cid).html('<span class="text-danger small"><i class="bi bi-clock-history"></i>' + COMMENT_JS_TIMER_OFF + '</span>');
} else {
// Если открыта форма редактирования, просто меняем текст, не удаляя кнопки
$('#timer_container_' + cid).html('<span class="text-warning small fw-bold"><i class="bi bi-exclamation-triangle"></i>' + COMMENT_JS_TIMER_OFF_A + '</span>');
}
return;
}
// Форматируем вывод 00:00
var minutes = Math.floor(timeLeft / 60);
var seconds = timeLeft % 60;
$timer.text(minutes + ':' + (seconds < 10 ? '0' : '') + seconds);
$timer.attr('data-left', timeLeft);
}, 1000);
$timer.data('interval-id', countdown);
});
}
/* Limit Plugin */
$.fn.extend({
limit: function(limit, element) {
return this.each(function() {
var self = $(this);
function substring() {
var val = self.val();
if (val.length > limit) self.val(val.substring(0, limit));
if (typeof element !== 'undefined') {
var remaining = limit - self.val().length;
$(element).html(remaining < 0 ? '0' : remaining);
}
}
self.on('focus keyup paste', substring);
substring();
});
}
});
function getCaptha() {
var now = new Date();
$('#captcha img').attr('src', aveabspath + 'inc/captcha.php?cd=' + now.getTime());
}
// ВАЛИДАЦИЯ ПОЛЕЙ
// Функция для проверки файла (расширение и размер)
function checkFileConsistency(file, $errorDisplay) {
if (!file) return true;
// Получаем настройки из шаблона
var allowedExts = (typeof ALLOWED_EXTENSIONS !== 'undefined') ? ALLOWED_EXTENSIONS.toLowerCase().split(',') : ['jpg', 'jpeg', 'png', 'gif, webp'];
var maxSizeKb = (typeof MAX_FILE_SIZE_KB !== 'undefined') ? parseInt(MAX_FILE_SIZE_KB) : 2048;
var fileName = file.name.toLowerCase();
var fileExt = fileName.split('.').pop();
var fileSizeKb = file.size / 1024;
// Проверка расширения
if ($.inArray(fileExt, allowedExts) === -1) {
$errorDisplay.text(COMMENT_JS_FILE_TYPE + ' .' + fileExt + ' ' + COMMENT_JS_FILE_TYPE_A).removeClass('d-none');
return false;
}
// Проверка размера
if (fileSizeKb > maxSizeKb) {
$errorDisplay.text(COMMENT_JS_FILE_SIZE + ' (' + Math.round(fileSizeKb) + COMMENT_JS_FILE_SIZE_KB + '). ' + COMMENT_JS_FILE_SIZE_MAX + ' ' + maxSizeKb + COMMENT_JS_FILE_SIZE_KB).removeClass('d-none');
return false;
}
$errorDisplay.addClass('d-none').text('');
return true;
}
function validate(form) {
var isValid = true;
// Вспомогательная функция для подсветки
function setInvalid($el, show, msg) {
if (show) {
$el.addClass('is-invalid');
if ($el.next('.invalid-feedback').length === 0) {
$el.after('<div class="invalid-feedback">' + msg + '</div>');
}
isValid = false;
} else {
$el.removeClass('is-invalid');
$el.next('.invalid-feedback').remove();
}
}
// Проверка Имени
setInvalid($(form.comment_author_name), !form.comment_author_name.value.trim(), COMMENT_JS_CHECK_NAME);
// Проверка Email
setInvalid($(form.comment_author_email), !form.comment_author_email.value.trim(), COMMENT_JS_CHECK_EMAIL);
// Проверка Доп. поля №1 (если оно обязательное)
if (typeof REQ_F1 !== 'undefined' && REQ_F1 == '1') {
var f1 = $('#in_author_website');
setInvalid(f1, !f1.val().trim(), COMMENT_JS_FILL + " " + NAME_F1);
}
// Проверка Доп. поля №2 (если оно обязательное)
if (typeof REQ_F2 !== 'undefined' && REQ_F2 == '1') {
var f2 = $('#in_author_city');
setInvalid(f2, !f2.val().trim(), COMMENT_JS_FILL + " " + NAME_F2);
}
// Проверка Текста
setInvalid($(form.comment_text), !form.comment_text.value.trim(), COMMENT_JS_ADD_COMMENT_TEXT);
// Проверка файлов в основной форме (Мультизагрузка)
var $fileInput = $(form).find('#comment_image');
if ($fileInput.length && $fileInput[0].files.length > 0) {
var files = $fileInput[0].files;
for (var i = 0; i < files.length; i++) {
if (!checkFileConsistency(files[i], $('#file_error'))) {
$fileInput.addClass('is-invalid');
isValid = false;
break;
}
}
}
if (!isValid) {
// Скроллим к первой ошибке
$('html, body').animate({
scrollTop: $('.is-invalid:first').offset().top - 130
}, 300);
}
return isValid;
}
/* --- ДЕЙСТВИЯ (DELETE, LOCK, OPEN/CLOSE, VOTE) --- */
function cAction(obj, action) {
var $btn = $(obj);
var cid = $btn.data('id');
if (!cid && action !== 'open' && action !== 'close') return;
$.get(aveabspath + 'index.php', {
module: 'comment',
action: action,
docid: typeof DOC_ID !== 'undefined' ? DOC_ID : '',
Id: cid ? cid : ''
}, function() {
if (action === 'close') {
$btn.attr('id', 'mod_comment_open')
.removeClass('btn-outline-danger').addClass('btn-outline-success')
.html('<i class="bi bi-lock-fill me-1"></i> ' + COMMENT_SITE_OPEN);
$('#mod_comment_new').fadeOut(300);
}
else if (action === 'open') {
$btn.attr('id', 'mod_comment_close')
.removeClass('btn-outline-success').addClass('btn-outline-danger')
.html('<i class="bi bi-unlock-fill me-1"></i> ' + COMMENT_SITE_CLOSE);
$('#mod_comment_new').fadeIn(300);
}
if (action === 'delete') {
var $commentBlock = $btn.closest('.mod_comment_comment');
$commentBlock.fadeOut(300, function() { $(this).remove(); });
}
// показать / скрыть комментарий
// показать / скрыть комментарий
if (action === 'unlock' || action === 'lock') {
var $icon = $btn.find('i');
var $card = $btn.closest('.mod_comment_comment');
if (action === 'lock') {
// --- Статус 0: СКРЫТО ---
$icon.attr('class', 'bi bi-eye-slash');
$btn.removeClass('text-success text-muted').addClass('text-danger').attr('title', COMMENT_ICON_SHOW);
$card.addClass('opacity-75 border-warning');
// Ищем плашку. Если её нет — создаем один раз.
var $alert = $card.find('.alert-warning');
if ($alert.length === 0) {
var alertHtml = '<div class="alert alert-warning py-1 px-2 mb-2 small d-flex align-items-center border-0 shadow-sm" style="border-left: 4px solid #ffc107 !important; display:none;">' +
'<i class="bi bi-clock-history me-2 text-dark"></i>' +
'<span class="text-dark">' + COMMENT_WAITING_MODERATION + '</span>' +
'</div>';
$card.find('.flex-grow-1').prepend(alertHtml);
$alert = $card.find('.alert-warning');
}
$alert.stop(true, true).fadeIn(300);
} else {
// --- Статус 1: ВИДИМО ---
$icon.attr('class', 'bi bi-eye');
$btn.removeClass('text-danger text-muted').addClass('text-success').attr('title', COMMENT_ICON_HIDE);
$card.removeClass('opacity-75 border-warning');
// Надежное скрытие: используем callback, чтобы после анимации точно убрать элемент
$card.find('.alert-warning').stop(true, true).fadeOut(300, function() {
$(this).remove(); // Удаляем плашку из кода совсем, чтобы при следующем "скрыть" она создалась чисто
});
}
}
});
}
/* --- ИНИЦИАЛИЗАЦИЯ --- */
$(document).ready(function() {
initCommentTimers();
var $doc = $(document);
// Убираем красную рамку, когда пользователь начинает исправлять поле
$doc.on('input change', '.form-control', function() {
$(this).removeClass('is-invalid');
$(this).next('.invalid-feedback').fadeOut(200, function() { $(this).remove(); });
});
// Инициализация тултипов для всех элементов с data-bs-toggle="tooltip"
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
// Счетчик: Количество оставшихся символов 1000 для формы создания комментария
// Используем переменную MAX_CHARS, которая приходит из настроек модуля
$('#in_message').limit(MAX_CHARS, '#charsLeft_new');
$doc.on('change', '#comment_image', function() {
var input = this;
var $errorBox = $('#file_error');
var $previewWrapper = $('#image_preview_wrapper');
$previewWrapper.empty().addClass('d-none');
$errorBox.addClass('d-none');
$(input).removeClass('is-invalid');
if (input.files && input.files.length > 0) {
var files = Array.from(input.files);
$previewWrapper.removeClass('d-none').addClass('d-flex flex-wrap gap-2');
files.forEach(function(file) {
if (checkFileConsistency(file, $errorBox)) {
var reader = new FileReader();
reader.onload = function(e) {
var imgHtml = `
<div class="position-relative preview-item">
<img src="${e.target.result}" class="img-thumbnail" style="max-height: 100px; min-width: 100px; object-fit: cover;">
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-new-img"
style="padding: 0 5px; margin: 2px;" title="${COMMENT_MOD_DEL_DEL}">
<i class="bi bi-x-lg"></i>
</button>
</div>`;
$previewWrapper.append(imgHtml);
}
reader.readAsDataURL(file);
} else {
$(input).val('').addClass('is-invalid');
$previewWrapper.addClass('d-none');
}
});
}
});
// обработчик, доработанный под мультизагрузку
$doc.on('click', '#remove_image_btn', function() {
$('#comment_image').val(''); // Очищаем выбор файлов
$('#image_preview_wrapper').addClass('d-none').empty(); // Скрываем и полностью очищаем блок превью
});
// удаление картинок по одной (крестиком на самой картинке)
$doc.on('click', '.remove-new-img', function() {
$(this).closest('.preview-item').remove(); // Удаляем только этот контейнер с картинкой
// Если после удаления картинок внутри не осталось — скрываем обертку и чистим инпут
if ($('#image_preview_wrapper').children().length === 0) {
$('#image_preview_wrapper').addClass('d-none');
$('#comment_image').val('');
}
});
// --- ВЫБОР РЕЙТИНГА В ФОРМЕ ---
$doc.on('mouseenter', '.star-choice', function() {
var val = $(this).data('value');
$(this).parent().find('.star-choice').each(function() {
if ($(this).data('value') <= val) {
$(this).removeClass('bi-star').addClass('bi-star-fill');
} else {
$(this).removeClass('bi-star-fill').addClass('bi-star');
}
});
});
$doc.on('mouseleave', '.rating-edit-block, #rating_wrapper', function() {
var $block = $(this);
var currentRating;
if ($block.attr('id') === 'rating_wrapper') {
currentRating = parseInt($('#comment_user_rating').val()) || 0;
} else {
var cid = $block.find('input[type="hidden"]').attr('id').replace('rating_input_', '');
currentRating = parseInt($('#rating_input_' + cid).val()) || 0;
}
$block.find('.star-choice').each(function() {
if ($(this).data('value') <= currentRating) {
$(this).removeClass('bi-star').addClass('bi-star-fill');
} else {
$(this).removeClass('bi-star-fill').addClass('bi-star');
}
});
});
$doc.on('click', '.star-choice', function() {
var val = $(this).data('value');
var ratingInput = $(this).closest('.rating-edit-block').find('input[type="hidden"]');
if (ratingInput.length) {
ratingInput.val(val);
} else {
$('#comment_user_rating').val(val);
}
$(this).prevAll().addBack().removeClass('bi-star').addClass('bi-star-fill');
$(this).nextAll().removeClass('bi-star-fill').addClass('bi-star');
});
$doc.on('click', '#buttonReset', function() {
$('#comment_user_rating').val(0);
$('#user_rating_stars .star-choice').removeClass('bi-star-fill').addClass('bi-star');
$('#rating_wrapper').show(); // Показываем блок рейтинга обратно
$('#image_preview_wrapper').addClass('d-none');
$('#charsLeft_new').text(MAX_CHARS);
$('.form-control').removeClass('is-invalid');
$('.invalid-feedback').remove();
$('#parent_id').val(''); // Очищаем ID родителя, чтобы комментарий снова стал корневым
});
$doc.on('click', '#reset_stars', function(e) {
e.preventDefault();
$('#comment_user_rating').val(0);
$('#user_rating_stars .star-choice').removeClass('bi-star-fill').addClass('bi-star');
});
$doc.on('click', '.reset-edit-stars', function(e) {
e.preventDefault();
var cid = $(this).data('id');
$('#rating_input_' + cid).val(0);
$('#user_rating_stars_' + cid + ' .star-choice').removeClass('bi-star-fill').addClass('bi-star');
});
// --- ГОЛОСОВАНИЕ (РЕЙТИНГ УЖЕ ОПУБЛИКОВАННЫХ) ---
$doc.on('mouseenter', '.star-item', function() {
var $parent = $(this).parent();
if ($parent.hasClass('comment-like')) {
$(this).css('transform', 'scale(1.2)');
}
else {
$(this).prevAll().addBack().removeClass('bi-star').addClass('bi-star-fill');
$(this).nextAll().removeClass('bi-star-fill').addClass('bi-star');
}
});
$doc.on('mouseleave', '.star-item', function() {
var $parent = $(this).parent();
if ($parent.hasClass('comment-like')) { $(this).css('transform', 'scale(1)'); }
});
$doc.on('click', '.star-item', function() {
var $container = $(this).closest('.comment-rating-container');
var commentId = $container.data('id');
var voteValue = $(this).data('value');
$.ajax({
url: 'index.php?module=comment&action=vote&ajax=1',
type: 'POST',
data: { comment_id: commentId, vote: voteValue, ajax: 1 },
success: function(response) {
var res = response.toString().trim();
if (res.indexOf('success') !== -1) {
$container.html('<span class="text-success small fw-bold">' + COMMENT_JS_THX + '</span>');
setTimeout(function(){ location.reload(); }, 1000);
} else if (res.indexOf('already_voted') !== -1) {
alert(COMMENT_JS_VOTE_A);
} else if (res.indexOf('own_comment') !== -1) {
alert(COMMENT_JS_VOTE_B);
} else if (res.indexOf('forbidden_anon') !== -1) {
alert(COMMENT_JS_VOTE_C);
} else {
alert(COMMENT_JS_VOTE_ERR);
}
},
error: function(xhr) {alert(COMMENT_JS_VOTE_ERR_A + ' ' + xhr.status);}
});
});
$doc.on('click', '#mod_comment_close', function(e) { e.preventDefault(); cAction(this, 'close'); });
$doc.on('click', '#mod_comment_open', function(e) { e.preventDefault(); cAction(this, 'open'); });
// КНОПКА ОТВЕТА
$doc.off('click', '.mod_comment_answer').on('click', '.mod_comment_answer', function(e) {
e.preventDefault();
var cid = $(this).data('id');
var $form = $('#mod_comment_new');
$form.insertAfter('#comment_' + cid).show();
$('#parent_id').val(cid);
// --- СКРЫТИЕ РЕЙТИНГА ДЛЯ ОТВЕТОВ ---
if (typeof SHOW_USER_RATING_REPLIES !== 'undefined' && SHOW_USER_RATING_REPLIES == '0') {
$('#rating_wrapper').hide(); // Скрываем весь блок рейтинга
$('#comment_user_rating').val(0); // Сбрасываем значение
$('#user_rating_stars .star-choice').removeClass('bi-star-fill').addClass('bi-star');
} else {
$('#rating_wrapper').show();
}
// --- КОНЕЦ ---
$('html, body').animate({ scrollTop: $form.offset().top - 150 }, 500);
$('#in_message').focus();
});
// Удаление
$doc.off('click', '.mod_comment_delete').on('click', '.mod_comment_delete', function(e) {
e.preventDefault();
if (confirm(COMMENT_JS_DELDEL_CONFIRM)) cAction(this, 'delete');
});
// Универсальный обработчик для переключателя видимости
// Оставляем все селекторы для надежности
$doc.off('click', '.mod_comment_toggle, .mod_comment_lock, .mod_comment_unlock')
.on('click', '.mod_comment_toggle, .mod_comment_lock, .mod_comment_unlock', function(e) {
e.preventDefault();
var $btn = $(this);
var $icon = $btn.find('i');
// Теперь проверяем перечеркнутый глаз (скрыто) или закрытый замок (на случай если остался в кэше)
// Если глаз перечеркнут ИЛИ замок закрыт — значит в базе 0, шлем 'unlock'
var isHidden = $icon.hasClass('bi-eye-slash') || $icon.hasClass('bi-lock-fill');
var actionToSend = isHidden ? 'unlock' : 'lock';
cAction(this, actionToSend);
});
// НАЧАЛО $doc.off('click', '.mod_comment_edit') РЕДАКТИРОВАНИЕ КОММЕНТАРИЯ
$doc.off('click', '.mod_comment_edit').on('click', '.mod_comment_edit', function(e) {
e.preventDefault();
var cid = $(this).data('id');
var $wrapper = $('#comment_wrapper_' + cid);
if ($wrapper.length === 0) {
console.error("Обертка #comment_wrapper_" + cid + " не найдена!");
return;
}
var $textBlock = $wrapper.find('.mod_comment_text').first();
// Пытаемся найти блок с картинками
var $attachedImagesBlock = $wrapper.find('.mod_comment_attached_image');
if ($attachedImagesBlock.length === 0) {
$attachedImagesBlock = $wrapper.find('.comment-files, .attached-images');
}
if ($wrapper.find('.edit-form-container').length > 0) return;
var isMyOwn = $wrapper.attr('data-is-own') == '1';
var currentRating = parseInt($wrapper.attr('data-user-rating')) || 0;
$('.edit-form-container').remove();
$('.mod_comment_text').show();
$('.mod_comment_attached_image, .comment-files').show();
var cleanText = $textBlock.html().replace(/<br\s*\/?>/mg, "\n").trim();
// --- СБОР ТЕКУЩИХ ВЛОЖЕНИЙ ---
var existingImagesHtml = '';
var $imageContainer = $('#image_container_' + cid);
// Ищем именно "карточки" из шаблона (класс .comment-image-item)
var $foundItems = $imageContainer.find('.comment-image-item');
if ($foundItems.length > 0) {
existingImagesHtml = '<div class="d-flex flex-wrap gap-2 mb-3 p-2 bg-white border rounded border-dashed">';
$foundItems.each(function(index) {
var $item = $(this);
var $link = $item.find('a');
var fileUrl = $link.attr('href');
if (!fileUrl) return;
var fileName = fileUrl.split('/').pop();
var fileExt = fileName.split('.').pop().toLowerCase();
var isImg = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt);
// Используем стиль превью
var previewContent = isImg
? `<img src="${fileUrl}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover;">`
: `<div class="d-flex align-items-center justify-content-center bg-light text-dark rounded border small fw-bold"
style="width: 80px; height: 80px; text-transform: uppercase;">${fileExt}</div>`;
existingImagesHtml += `
<div id="existing_wrapper_${cid}_${index}" class="position-relative">
${previewContent}
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-existing-img"
data-id="${cid}" data-img-path="${fileName}" data-target="existing_wrapper_${cid}_${index}"
style="padding: 0 4px; margin: 1px; line-height: 1; z-index: 10;">
<i class="bi bi-x-lg" style="font-size: 0.7rem;"></i>
</button>
</div>`;
});
existingImagesHtml += '</div>';
}
// --- РЕЙТИНГ ---
var starsEditBlock = '';
var parentIdVal = parseInt($wrapper.attr('data-parent')) || 0;
var isReply = parentIdVal > 0;
var ratingForbiddenForReply = (isReply && typeof SHOW_USER_RATING_REPLIES !== 'undefined' && SHOW_USER_RATING_REPLIES == '0');
if (typeof SHOW_USER_RATING !== 'undefined' && SHOW_USER_RATING == '1' && isMyOwn && !ratingForbiddenForReply) {
if (typeof UGROUP !== 'undefined' && (UGROUP != '2' || RATING_ANON_SET == '1')) {
var starsHtml = '';
for(var i=1; i<=5; i++) {
var starClass = (i <= currentRating) ? 'bi-star-fill' : 'bi-star';
starsHtml += `<i class="star-choice ${starClass}" data-value="${i}" style="cursor: pointer; color: #ffc107; font-size: 1.2rem; margin-right: 2px;"></i>`;
}
starsEditBlock = `
<div class="mb-2 rating-edit-block">
<label class="small text-muted d-block mb-1">${COMMENT_JS_RATING_ST_EDIT}</label>
<div class="d-flex align-items-center">
<div id="user_rating_stars_${cid}">${starsHtml}</div>
<a href="javascript:void(0);" class="reset-edit-stars ms-3 text-decoration-none small text-muted" data-id="${cid}">
<i class="bi bi-x-circle"></i> ${COMMENT_JS_RATING_ST_EDIT_A}
</a>
</div>
<input type="hidden" name="comment_user_rating" id="rating_input_${cid}" value="${currentRating}" />
</div>`;
}
}
// --- СБОРКА ФОРМЫ ---
// --- ПРОВЕРКА ПРАВ НА ЗАГРУЗКУ ---
var canUploadFiles = false;
if (typeof ALLOW_FILES !== 'undefined' && ALLOW_FILES == '1') {
if (typeof UGROUP !== 'undefined' && UGROUP == '2') {
if (typeof ALLOW_FILES_ANON !== 'undefined' && ALLOW_FILES_ANON == '1') canUploadFiles = true;
} else {
canUploadFiles = true;
}
}
var uploadBlockHtml = '';
if (canUploadFiles) {
uploadBlockHtml = `
<div class="mb-2 border-top pt-2">
<label class="small text-muted mb-1 d-block fw-bold">${COMMENT_JS_ADD_NEW_FILES}</label>
<div id="file_error_${cid}" class="js-file-error text-danger small fw-bold mb-1" style="display:none;"></div>
<input type="file" name="comment_image[]" id="new_file_${cid}" class="form-control form-control-sm mb-1" multiple>
<div class="text-muted" style="font-size: 0.7rem;">
<i class="bi bi-info-circle"></i> ${ALLOWED_EXTENSIONS.replace(/,/g, ', ')} | до ${MAX_FILE_SIZE_KB} KB
</div>
</div>`;
}
var editHtml = `
<div class="edit-form-container border rounded p-3 bg-light mt-2 mb-2" data-cid="${cid}">
<textarea rows="5" id="ta_${cid}" class="form-control mb-3">${cleanText}</textarea>
${starsEditBlock}
${existingImagesHtml !== '' ? `
<p class="small text-muted mb-1">${COMMENT_JS_LOADED_FILES}</p>
${existingImagesHtml}
` : ''}
${uploadBlockHtml}
<input type="hidden" name="delete_files" id="delete_files_${cid}" value="">
<div class="d-flex gap-2 mt-3 align-items-center">
<button type="button" class="btn btn-sm btn-primary saveButton" data-id="${cid}">${COMMENT_JS_EDIT_BUT_SAVE}</button>
<button type="button" class="btn btn-sm btn-secondary cancelButton">${COMMENT_JS_EDIT_BUT_CANCEL}</button>
<small class="ms-auto text-muted">${COMMENT_JS_EDIT_BUT_LEFT} <span id="charsLeft_${cid}"></span></small>
</div>
</div>`;
// Прячем текст комментария
$textBlock.hide();
// Прячем ВЕСЬ контейнер с файлами
$('#image_container_' + cid).attr('style', 'display: none !important;');
// Для надежности скрываем всё остальное
$wrapper.find('.mod_comment_attached_images, .comment-files').hide();
$textBlock.after(editHtml);
// --- НАКОПИТЕЛЬНОЕ ПРЕВЬЮ НОВЫХ ФАЙЛОВ ---
var pendingFiles = []; // Массив для хранения всех выбранных файлов
$wrapper.find('.edit-form-container').data('pendingFiles', pendingFiles);
// Проверяем права, прежде чем вешать обработчик
if (canUploadFiles) {
$('#new_file_' + cid).on('change', function() {
var files = this.files;
var $input = $(this);
var $currentEditForm = $input.closest('.edit-form-container');
var $errorDisplay = $currentEditForm.find('.js-file-error');
var maxLimit = parseInt(typeof MAX_FILES_COUNT !== 'undefined' ? MAX_FILES_COUNT : 5);
if (files) {
$errorDisplay.text('').hide();
$.each(files, function(i, file) {
var alreadyInPost = $currentEditForm.find('[id^="existing_wrapper_"]').length;
var inPending = pendingFiles.filter(f => f !== null).length;
if ((alreadyInPost + inPending) >= maxLimit) {
$errorDisplay.text(' ' + COMMENT_JS_LIMIT_AB + ' ' + maxLimit + ' ' + COMMENT_JS_LIMIT_ABC).show();
return false;
}
// ПРОВЕРКА ЧЕРЕЗ ФУНКЦИЮ (она берет ALLOWED_EXTENSIONS)
if (!checkFileConsistency(file, $errorDisplay)) return true;
pendingFiles.push(file);
var currentIndex = pendingFiles.length - 1;
var fileExt = file.name.split('.').pop().toLowerCase();
var isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt);
var reader = new FileReader();
reader.onload = function(e) {
var $previewContainer = $('#new_files_preview_' + cid);
if ($previewContainer.length === 0) {
$input.after(`<div id="new_files_preview_${cid}" class="d-flex flex-wrap gap-2 mt-2"></div>`);
$previewContainer = $('#new_files_preview_' + cid);
}
var content = isImage
? `<img src="${e.target.result}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover; border: 2px solid #0d6efd;">`
: `<div class="d-flex align-items-center justify-content-center bg-light text-dark rounded border shadow-sm"
style="width: 80px; height: 80px; font-weight: bold; text-transform: uppercase; font-size: 12px; border: 2px solid #0d6efd !important;">${fileExt}</div>`;
$previewContainer.append(`
<div class="position-relative new-file-item" data-index="${currentIndex}">
${content}
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-new-file" style="padding: 0 5px; margin: 2px; line-height: 1;">
<i class="bi bi-x-lg" style="font-size: 0.7rem;"></i>
</button>
</div>`);
};
if (isImage) reader.readAsDataURL(file);
else reader.onload({target: {result: ''}});
});
}
});
}
// Обработчик удаления НОВОГО файла из превью (до сохранения)
$doc.off('click', '.remove-new-file').on('click', '.remove-new-file', function() {
var $item = $(this).closest('.new-file-item');
var index = $item.data('index');
pendingFiles[index] = null; // Помечаем как удаленный
$item.remove();
// Ищем через ближайший контейнер формы, чтобы точно попасть в нужный блок текста
$(this).closest('.edit-form-container').find('.js-file-error').hide().text('');
});
// --- КОНЕЦ ВСТАВКИ ---
// Добавил поддержку лимита символов
if ($.fn.limit) {
$('#ta_' + cid).limit(MAX_CHARS, '#charsLeft_' + cid);
}
$('#ta_' + cid).focus();
});
// КОНЕЦ $doc.off('click', '.mod_comment_edit')
$doc.on('click', '.remove-existing-img', function() {
var cid = $(this).data('id');
var imgPath = $(this).data('img-path');
var targetId = $(this).data('target');
// 1. Очищаем текст ошибки
$('#file_error_' + cid).addClass('d-none').text('');
var $deleteInput = $('#delete_files_' + cid);
var currentDeleteList = $deleteInput.val() ? $deleteInput.val().split(',') : [];
if (currentDeleteList.indexOf(imgPath) === -1) {
currentDeleteList.push(imgPath);
$deleteInput.val(currentDeleteList.join(','));
}
// 2. Скрываем и удвляем элемент, чтобы счетчик .length его больше не видел
$('#' + targetId).fadeOut(300, function() {
$(this).remove();
});
});
// Обработка выбора НОВОГО файла при редактировании
$doc.on('change', 'input[id^="new_file_"]', function() {
var cid = $(this).attr('id').replace('new_file_', '');
var input = this;
// 1. Сначала находим блок
var $errorBox = $('#file_error_' + cid);
var $previewWrapper = $('#edit_preview_wrapper_' + cid);
if (input.files && input.files.length > 0) {
$previewWrapper.find('.preview-item-new').remove();
$previewWrapper.removeClass('d-none');
Array.from(input.files).forEach(function(file) {
if (checkFileConsistency(file, $errorBox)) {
// Если файл прошел проверку - только тогда можно спрятать ошибку
$errorBox.addClass('d-none').text('');
var reader = new FileReader();
reader.onload = function(e) {
var item = `
<div class="position-relative preview-item-new">
<img src="${e.target.result}" class="img-thumbnail" style="max-height: 100px;">
</div>`;
$previewWrapper.append(item);
}
reader.readAsDataURL(file);
}
});
}
});
// Удаление НОВОГО выбранного файла в режиме редактирования
$doc.on('click', '.remove-edit-new-img', function() {
var cid = $(this).data('id');
$('#new_file_' + cid).val('');
$('#edit_preview_wrapper_' + cid).addClass('d-none');
});
// Обработка кнопки "Отмена" при редактировании
$doc.on('click', '.cancelButton', function() {
var $container = $(this).closest('.edit-form-container');
var $wrapper = $container.closest('[id^="comment_wrapper_"]');
var cid = $container.data('cid'); // Берем ID для обращения к контейнеру файлов
// 1. Возвращаем текст
$wrapper.find('.mod_comment_text').show();
// 2. Возвращаем оригинальный блок с картинками
// СНАЧАЛА ЧИСТИМ ИНЛАЙН СТИЛЬ
$('#image_container_' + cid).attr('style', '');
// Затем стандартный показ
$wrapper.find('.mod_comment_attached_image, .mod_comment_attached_images, .comment-files, .attached-images').show();
// 3. Если мы скрывали изображения напрямую через $foundImgs.hide(), показываем их
$wrapper.find('img').show();
// И ссылки
$wrapper.find('a').show();
// 4. Удаляем форму редактирования
$container.remove();
});
// Кнопка сохранить редактирование
$doc.off('click', '.saveButton').on('click', '.saveButton', function() {
var $btn = $(this);
var cid = $btn.data('id');
var $container = $btn.closest('.edit-form-container');
// 1. Повторно определяем права внутри кнопки (чтобы избежать undefined)
var canUploadFilesLocal = false;
if (typeof ALLOW_FILES !== 'undefined' && ALLOW_FILES == '1') {
if (typeof UGROUP !== 'undefined' && UGROUP == '2') {
if (typeof ALLOW_FILES_ANON !== 'undefined' && ALLOW_FILES_ANON == '1') canUploadFilesLocal = true;
} else {
canUploadFilesLocal = true;
}
}
// 2. Достаем накопительный массив файлов
var pendingFiles = $container.data('pendingFiles') || [];
var fd = new FormData();
fd.append('module', 'comment');
fd.append('action', 'edit');
fd.append('Id', cid);
fd.append('text', $('#ta_' + cid).val());
var rInput = $('#rating_input_' + cid);
if (rInput.length) fd.append('user_rating', rInput.val());
fd.append('delete_files', $('#delete_files_' + cid).val());
// 3. Добавляем файлы из массива (используем локальную проверку прав)
if (canUploadFilesLocal && pendingFiles.length > 0) {
pendingFiles.forEach(function(file) {
if (file !== null) {
fd.append('comment_image[]', file);
}
});
}
$.ajax({
url: aveabspath + 'index.php?ajax=1',
type: 'POST',
data: fd,
processData: false,
contentType: false,
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
var pct = Math.round((evt.loaded / evt.total) * 100);
// Проверяем наличие контейнера прогресса, если его нет в HTML — создаем или пропускаем
var $pBar = $('#edit_progress_bar_' + cid);
if ($pBar.length) {
$('#edit_progress_container_' + cid).removeClass('d-none');
$pBar.css('width', pct + '%');
}
}
}, false);
return xhr;
},
beforeSend: function() {
$btn.prop('disabled', true).text('...');
},
success: function(response) {
// Если PHP вернул ошибку лимита, выведем её вместо перезагрузки
if (response === 'MAX_FILES_LIMIT_EXCEEDED') {
alert('Превышен лимит количества файлов!');
$btn.prop('disabled', false).text('Сохранить');
} else {
location.reload();
}
},
error: function() {
$btn.prop('disabled', false).text('Ошибка');
}
});
});
// Глобальный массив для файлов НОВОГО комментария
var newCommentPendingFiles = [];
// Обработка выбора файлов в основной форме
// .off('change') чтобы не было дублей
$doc.off('change', '#comment_image').on('change', '#comment_image', function() {
var files = this.files;
var $previewWrapper = $('#image_preview_wrapper');
var $errorDisplay = $('#file_error');
// 1. Лимит из настроек
var maxLimit = parseInt(typeof MAX_FILES_COUNT !== 'undefined' ? MAX_FILES_COUNT : 5);
if (files) {
$errorDisplay.addClass('d-none').text(''); // Сбрасываем старые ошибки
$.each(files, function(i, file) {
// 2. Считаем, сколько сейчас РЕАЛЬНО файлов в очереди (не null)
var currentInQueue = newCommentPendingFiles.filter(function(f) { return f !== null; }).length;
// 3. ПРОВЕРКА ЛИМИТА: если уже достигли максимума
if (currentInQueue >= maxLimit) {
$errorDisplay.removeClass('d-none').text(COMMENT_JS_LIMIT_ABCD + maxLimit + ' ' + COMMENT_JS_LIMIT_ABCDE);
return false; // break - полностью выходим из цикла $.each
}
// 4. Валидация (размер, расширение)
if (typeof checkFileConsistency === 'function') {
if (!checkFileConsistency(file, $errorDisplay)) {
return true; // continue - этот файл плохой, идем к следующему
}
}
// Если прошли все проверки — добавляем в очередь
newCommentPendingFiles.push(file);
var currentIndex = newCommentPendingFiles.length - 1;
var reader = new FileReader();
reader.onload = function(e) {
if ($previewWrapper.find(`[data-index="${currentIndex}"]`).length > 0) return;
// Определяем, что вставить: картинку или заглушку с текстом расширения
var fileExt = file.name.split('.').pop().toLowerCase();
var isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt);
// Формируем контент: либо твой оригинальный img, либо блок с расширением
var content = isImage
? `<img src="${e.target.result}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover; border: 2px solid #198754;">`
: `<div class="d-flex align-items-center justify-content-center bg-primary text-white rounded" style="width: 80px; height: 80px; font-weight: bold; text-transform: uppercase; border: 2px solid #0d6efd;">${fileExt}</div>`;
$previewWrapper.removeClass('d-none');
$previewWrapper.append(`
<div class="position-relative new-comment-file-item" data-index="${currentIndex}">
${content}
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-new-comment-img"
style="padding: 0 5px; margin: 2px; line-height: 1;">
<i class="bi bi-x-lg" style="font-size: 0.7rem;"></i>
</button>
</div>`);
};
reader.readAsDataURL(file);
});
}
// Очищаем инпут в любом случае, чтобы можно было выбрать те же файлы снова
$(this).val('');
});
// Удаление выбранного файла из очереди в новой форме
$doc.on('click', '.remove-new-comment-img', function() {
var $item = $(this).closest('.new-comment-file-item');
var index = $item.data('index');
newCommentPendingFiles[index] = null; // Помечаем удаленным
$('#file_error').addClass('d-none').text('');
$item.fadeOut(200, function() {
$(this).remove();
if ($('#image_preview_wrapper').children().length === 0) {
$('#image_preview_wrapper').addClass('d-none');
}
});
});
// Очистка при нажатии кнопки "Сбросить" (reset)
$doc.on('click', '#buttonReset', function() {
newCommentPendingFiles = [];
$('#image_preview_wrapper').empty().addClass('d-none');
// убрать текст ошибки
$('#file_error').addClass('d-none').text('');
});
// Отправка новой формы (или ответа)
$doc.on('submit', '#mod_comment_new form', function(e) {
e.preventDefault();
if (!validate(this)) return false;
var $form = $(this);
var $btn = $form.find('[type="submit"]');
var originalBtnText = $btn.text();
var formData = new FormData(this);
// 1. Удаляем стандартные значения, чтобы они не дублировались из инпута
formData.delete('comment_image');
formData.delete('comment_image[]');
// 2. Добавляем файлы из нашего накопленного массива newCommentPendingFiles
if (newCommentPendingFiles && newCommentPendingFiles.length > 0) {
newCommentPendingFiles.forEach(function(file) {
if (file !== null) {
formData.append('comment_image[]', file);
}
});
}
$.ajax({
url: aveabspath + 'index.php?ajax=1',
type: 'POST',
data: formData,
processData: false,
contentType: false,
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
var pct = Math.round((evt.loaded / evt.total) * 100);
$('#upload_progress_container').removeClass('d-none');
$('#upload_progress_bar').css('width', pct + '%');
$('#upload_status_text').text(COMMENT_JS_LOADS_F + ' ' + pct + '%');
}
}, false);
return xhr;
},
beforeSend: function() { $btn.prop('disabled', true).text(COMMENT_JS_LOADS_R); },
success: function(data) {
if (data.includes('wrong_securecode')) {
alert(COMMENT_JS_SEC_CODE_WRONG);
getCaptha();
$btn.prop('disabled', false).text(originalBtnText);
$('#upload_progress_container').addClass('d-none');
} else {
// Очищаем массив перед перезагрузкой
newCommentPendingFiles = [];
$form[0].reset(); // Чистим текст и инпуты
$form.find('input[name="parent_id"]').val('0'); // Сбрасываем "режим ответа"
location.reload();
}
},
error: function() {
alert(COMMENT_JS_ERR_SRV);
$btn.prop('disabled', false).text(originalBtnText);
$('#upload_progress_container').addClass('d-none');
}
});
});
$doc.on('click', '#captcha img, #reload_captcha', function(e) { e.preventDefault(); getCaptha(); });
});
})(jQuery);
}
})();