Единый форум поддержки

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Единый форум поддержки » Новые возможности форумов » Запросы по скриптам #11


Запросы по скриптам #11

Сообщений 1741 страница 1748 из 1748

1

В этой теме просим о создании различных скриптов

Здесь вы можете оставить запрос на разработку нового скрипта для форума. Пожалуйста, внимательно ознакомьтесь с правилами темы. Просьба быть внимательным: эта тема касается только вопросов о создании новых скриптов!
Вопросы по оформлению форума и прочим CSS-кодам, просьба, задавать в темах Общие вопросы от новичков (63) #3 и Общие вопросы по оформлению (65) #2

Информируем о следующем:

  • Участники форума оказывают помощь в написании скриптов исключительно по собственному желанию.

  • Администрация форума не может гарантировать исполнение и корректность каждого запроса.

  • Пожалуйста, уважайте чужое время и усилия других пользователей, старайтесь писать грамотно и доходчиво.

  • Будьте взаимовежливы: Грубое или требовательное отношение к участникам форума неприемлемо и может привести к отказу в помощи на всём форуме.

  • Все вопросы, не касающиеся запросов скриптов, будут удаляться!

Сформулируйте свой запрос максимально подробно, чтобы повысить вероятность отклика:

  • Опишите желаемую функциональность: Что конкретно должен делать скрипт?

  • Укажите конечный результат: Что вы хотите получить в итоге? Где и как будет использоваться скрипт?

  • Если есть примеры, покажите: Укажите ссылки на схожий функционал скрипта или нарисуйте макет (на скриншоте), чего именно вы ожидаете получить - всё это значительно упростит задачу.

Если на ваш запрос долго нет ответа, пожалуйста, воздержитесь от повторных сообщений или обвинений. Возможно, задача оказалась слишком сложной или трудозатратой по времени, будьте готовы к тому, что участники могут предложить свои услуги на платной основе. Обсуждение условий и оплаты происходит между вами и участником (исполнителем) в личных сообщениях или иных средствах связи (но не в теме!). Администрация форума не несет никакой ответственности за подобные договоренности и их выполнение.

Если скрипт был написан на безвозмездной основе, автор может разместить его в общем каталоге скриптов для других пользователей.

Предыдущая тема - Запросы по скриптам[10]

Инструменты для поиска и редактирования стиля (скриптов).

Как с помощью браузера можно определить элемент дизайна.

Каталог скриптов/CSS

Полезные скрипты, необходимые темы для новичков, а также ссылки на сайты рассказывающие что такое НТМЛ и CSS.

Типовые Вопросы (ЧаВо)

Ответы на часто задаваемые вопросы.

Как правильно задавать вопросы. В чём разница между стилем и скриптом.

Плюс к названию темы еще и Памятка.

+1

1741

Амираль

Очень интересно, но, как всегда, ничего не понятно - какая-то кнопка в итоге должна появится или что?)
И как найти вас в телеграме?:)

0

1742

Амираль написал(а):

буду ультра благодарна тому, кто перевезет пост в скрипты от пользаков, idk как я уже второй раз вместо той темы закидываю сюда

Сделано специально, чтобы в скрипты от пользователей попадали только проверенные коды, а не любой публикуемый там пост (зачастую это вопросы, которые должны быть по идее адресованы в эту тему, или непроверенные/дублирующиеся коды). Перенёс в нужную тему.

+1

1743

Merlin777 написал(а):

⚡Мгновенные "Быстроплюсы" и ⚡Мгновенные "Сказали спасибо"

Здравствуйте! Поставил этот скрипт - всё работает прекрасно. Но. Почему-то в личной переписке в каждом письме появились "Сказали спасибо" - и  лайки от всё время разных форумчан (не одни и те же). Причём эти лайки есть абсолютно во всех письмах. И даже есть в тех письмах, которые были написаны, когда на форуме вообще ни одного скрипта не было установлено.

Скриншот

Отредактировано Виплич (Пн, 26 Янв 2026 13:18:47)

0

1744

Виплич
По идее нужно ограничить выполнение скрипта только для топиков, ID сообщений в топиках могут пересекаться с таковыми в ЛС, что в данном случае будет некорректно. Что-то типа такого, думаю (добавка выделена красным):

function scheduleUpdate(scope = document) {
    if (!$('#pun-viewtopic').length) return;

+1

1745

@Виплич

Вот версия (CSS + JavaScript) с проверкой работы на страницах топика:

Посмотреть код
Код:
<style>
/*Быстроплюсы для Топика!*/
div .post-rating p a {
    text-align: center;
    outline: 1px solid transparent;
    font-weight: 700;
    background: url(https://upforme.ru/uploads/001a/f0/7d/2/466540.png  ) no-repeat center;
     background-size:36px auto;
     position:relative;
    z-index:100;
    width: 36px;
    height: 36px;
    text-align:center;
    font-size: 12px !important;
    display: inline-block;
    vertical-align: middle;
    line-height: 34px;
    letter-spacing: -.1px;
}
.noNull:before{content: "+";
    display: inline-block;
    margin-left: -1px;
    font-size: 9px;
    letter-spacing: 0!important;
}
.punbb .post-rating p {
   padding: 0 !important;
}
</style>

<script>
    'use strict';
 
// --- ОБЩИЕ ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
 
// Получает ID постов из указанного DOM-контекста
function getPostIds(scope = document) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return [];
    
    const postElements = $(scope).find('div.post');
    return postElements.map(function() {
        const id = $(this).attr('id');
        if (id && id.startsWith('p')) {
            return id.slice(1);
        }
    }).get();
}
 
// --- ЧАСТЬ 1: БЫСТРОПЛЮСЫ ---
 
// Отслеживание уже обработанных постов (для избежания дублирования)
const processedPostsForPlus = new Set();
 
// Обновляет отображение цифры рейтинга и управляет классом noNull
function setDigit(th) {
    var d = parseInt(th.innerHTML);
    if (d > 0) $(th).addClass('noNull');
    else $(th).removeClass('noNull');
    th.innerHTML = d;
}
 
// Сохранение оригинального alert для восстановления после голосования
var Busy = window.alert;
 
// Обработка клика по кнопке "плюса": отправка запроса, обновление рейтинга и мини-профиля
function setPlus(sel) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    window.alert = null;
    var a = sel.prop('href');
    var pid = a.match(/\?id=(\d+)/)[1];
 
    var uid = sel.parents('.post').find('.pl-email a[href*="profile.php?"]').prop('href');
    if (uid) uid = uid.match(/\?id=(\d+)$/)[1];
    var v = a.match(/&v=(\d+)/)[1] == 0 ? -1 : 1;
    $('#post-' + pid + '-vote').hide();
 
    $.get(a + '&format=json', function(data) {
        if (data.error && data.error.message) {
            // Ошибка обрабатывается без уведомлений — только внутренняя логика
        } else if (data.delta) {
            if(data.response !== undefined) {
                $('#p' + pid + ' .post-rating a').text(data.response);
                setDigit($('#p' + pid + ' .post-rating a')[0]);
            } else {
                var oldRating = parseInt($('#p' + pid + ' .post-rating a').text()) || 0;
                var newRating = oldRating + data.delta;
                $('#p' + pid + ' .post-rating a').text(newRating);
                setDigit($('#p' + pid + ' .post-rating a')[0]);
            }
 
            // Обновление мини-профиля автора и текущего пользователя
            var $res = $('.pl-email a[href$="profile.php?id=' + uid + '"]').parents('.post').find('.pa-respect');
            var $pos = $('.pl-email a[href$="profile.php?id=' + UserID + '"]').parents('.post').find('.pa-positive');
 
            function replaceRating(sel, v, revert) {
                var html = $(sel).html(),
                    delta = v;
                if (revert) delta = delta > 0 ? -1 : 1;
                if (v > 0) {
                    html = html.replace(/\[\+(\d+)\//g, function(str, p1) {
                        return '[+' + (parseInt(p1) + delta) + '/';
                    });
                } else {
                    html = html.replace(/\/-(\d+)\]/g, function(str, p1) {
                        return '/-' + (parseInt(p1) - delta) + ']';
                    });
                }
                $(sel).html(html);
            }
            if ($res.html().indexOf('[') != -1) {
                $res.each(function() { replaceRating(this, v); });
                $pos.each(function() { replaceRating(this, v); });
                if (Math.abs(data.delta) == 2) {
                    v = v > 0 ? -1 : 1;
                    $res.each(function() { replaceRating(this, v, 1); });
                    $pos.each(function() { replaceRating(this, v, 1); });
                }
            } else {
                var d0 = $res.find('span:not(.fld-name)').html(),
                    p0 = $pos.find('span:not(.fld-name)').html();
                var d1 = parseInt(d0) + v;
                if (p0) {
                    var p1 = parseInt(p0) + v;
                }
                if (d1 && d1 > 0) {
                    d1 = '+' + d1;
                }
                if (p1 && p1 > 0) {
                    p1 = '+' + p1;
                }
                $res.find('span:not(.fld-name)').html(d1);
                if (p0) $pos.find('span:not(.fld-name)').html(p1);
            }
 
            // Генерация события для обновления списка "Спасибо" при лайке
            if(v === 1) {
                document.dispatchEvent(new CustomEvent('vote:happened', {
                    detail: { pid: pid, type: 'thank' }
                }));
            }
        }
        setTimeout(function() {window.alert = Busy;},1300);
    })
    .fail(function(xhr, status, error) {
        setTimeout(function() {window.alert = Busy;},1300);
    });
}
 
// Обёртка для вызова setPlus из onclick
window.BR = function(th) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    var lnk = $(th).parents('.post-box').find('.post-vote>p>a');
    setPlus(lnk);
};
 
// Скрывает все вновь добавленные элементы .post-vote
function hideAllNewPostVotes(scope = document) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    $(scope).find('.post-vote[id^="post-"]').each(function() {
        var postId = this.id.replace('post-', '');
        var postRatingLink = $(`.post#p${postId} .post-rating p a`);
        if(postRatingLink.length) {
             $(this).hide();
        }
    });
}
 
// Назначает обработчики для кнопок "плюса" в указанной области
function applyPlusHandlers(scope = document) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    $(scope).find('.post .post-rating p a').each(function() {
        if (!this.hasAttribute('data-plus-handler')) {
             this.setAttribute("onclick", "BR(this)");
             setDigit(this);
 
             var postVoteLinkSelector = $(this).parents('.post').attr('id');
             if(postVoteLinkSelector) {
                 var pid = postVoteLinkSelector.slice(1);
                 var postVoteLink = $('#post-' + pid + '-vote');
                 if(postVoteLink.length) {
                      postVoteLink.hide();
                 }
             }
             this.setAttribute('data-plus-handler', 'true');
        }
    });
    hideAllNewPostVotes(scope);
}
 
// --- ЧАСТЬ 2: СПИСОК ПОБЛАГОДАРИВШИХ (API-ЛОГИКА) ---
 
// Отслеживание постов, для которых уже запрашивались "спасибо"
const processedPostsForThanks = new Set();
 
// Отслеживание ID постов, для которых выполняется запрос (для дедупликации)
const pendingUpdates = new Set();
 
// Вставка списка благодаривших в пост
function insertThanksList(postId, thankersListHtml) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    if (!thankersListHtml) {
        return;
    }
 
    var postBox = $('.post#p' + postId + ' .post-box');
    if (postBox.length && postBox.find('.postVoters').length === 0) {
        var html = '<div class="postVoters"><strong><i><font color ="#13355e">Сказали спасибо</font></i>:</strong> ' + thankersListHtml + '</div>';
        postBox.append(html);
        processedPostsForThanks.add(postId);
    } else {
        if(postBox.length === 0) {
            var postElement = $('.post#p' + postId);
            if(postElement.length && postElement.find('.postVoters').length === 0) {
                var html = '<div class="postVoters"><strong><i><font color ="#13355e">Сказали спасибо</font></i>:</strong> ' + thankersListHtml + '</div>';
                postElement.append(html);
                processedPostsForThanks.add(postId);
            }
        } else {
            postBox.find('.postVoters').html('<strong><i><font color ="#13355e">Сказали спасибо</font></i>:</strong> ' + thankersListHtml);
        }
    }
}
 
// Обработка ответа API: группировка и вставка благодаривших
function processVotes(data) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    if (!data || !data.response) {
        const postIdsFromRequest = currentFetchRequestIds || [];
        postIdsFromRequest.forEach(id => processedPostsForThanks.add(id));
        currentFetchRequestIds = null;
        return;
    }
 
    const votesByPost = {};
    data.response.forEach(item => {
        const postId = item.post_id;
        if (item.votes && Array.isArray(item.votes)) {
            item.votes.forEach(vote => {
                if (vote.value === '1') {
                    if (!votesByPost[postId]) {
                        votesByPost[postId] = [];
                    }
                    votesByPost[postId].push(vote);
                }
            });
        }
    });
 
    Object.keys(votesByPost).forEach(postId => {
        const votesForThisPost = votesByPost[postId];
        let thankersHtml = '';
        votesForThisPost.forEach((vote, index) => {
            thankersHtml += '<a href="/profile.php?id=' + vote.user_id + '">' + vote.username + '</a>';
            if (index < votesForThisPost.length - 1) {
                thankersHtml += ', ';
            }
        });
        insertThanksList(postId, thankersHtml);
    });
 
    Object.keys(votesByPost).forEach(id => processedPostsForThanks.add(id));
    data.response.forEach(item => {
        if (!processedPostsForThanks.has(item.post_id)) {
            processedPostsForThanks.add(item.post_id);
        }
    });
}
 
// Выполнение API-запроса для получения списка благодаривших
let currentFetchRequestIds = null;
 
function fetchVotesForPosts(postIds) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    if (!postIds || postIds.length === 0) {
        postIds.forEach(id => pendingUpdates.delete(id));
        return;
    }
 
    const apiParams = {
        method: 'post.getVotesByPosts',
        post_id: postIds.join(','),
        fields: 'post_id,user_id,username,value,datetime',
        sort_dir: 'desc'
    };
 
    currentFetchRequestIds = [...postIds];
 
    return $.get('/api.php', apiParams, function(data) {
        processVotes(data);
    }, 'json')
    .fail(function(xhr, status, error) {
        postIds.forEach(id => processedPostsForThanks.add(id));
        currentFetchRequestIds = null;
    });
}
 
// Планирование обновления списка "спасибо" для новых постов
function scheduleUpdate(scope = document) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    requestAnimationFrame(() => {
        const newPostIds = getPostIds(scope).filter(id => !processedPostsForThanks.has(id));
        if (newPostIds.length > 0) {
            const idsToRequest = newPostIds.filter(id => !pendingUpdates.has(id));
            if (idsToRequest.length > 0) {
                idsToRequest.forEach(id => pendingUpdates.add(id));
                fetchVotesForPosts(idsToRequest).always(() => {
                    idsToRequest.forEach(id => pendingUpdates.delete(id));
                });
            }
        }
    });
}
 
// Объединённая функция резервного обновления (раз в секунду)
function combinedBackupUpdate() {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    applyPlusHandlers(document);
    scheduleUpdate(document);
}
 
// --- ИНИЦИАЛИЗАЦИЯ И ПОДПИСКИ НА СОБЫТИЯ ---
 
// Инициализация при загрузке DOM
$(function() {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    $('.post .post-rating p a').each(function() {
        this.setAttribute("onclick", "BR(this)");
        setDigit(this);
        var postVoteLinkSelector = $(this).parents('.post').attr('id');
        if(postVoteLinkSelector) {
            var pid = postVoteLinkSelector.slice(1);
            var postVoteLink = $('#post-' + pid + '-vote');
            if(postVoteLink.length && $(this).hasClass('noNull')) {
                postVoteLink.hide();
            }
        }
        this.setAttribute('data-plus-handler', 'true');
    });
 
    scheduleUpdate(document);
});
 
// Подписка на событие добавления нового поста (например, через AJAX)
document.addEventListener('pun_post', function(e) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    var scope = e.detail?.post || document.body;
    applyPlusHandlers(scope);
    scheduleUpdate(scope);
});
 
// Подписка на собственное событие после успешного лайка
document.addEventListener('vote:happened', function(e) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    if (e.detail && e.detail.pid && e.detail.type === 'thank') {
        var pid = e.detail.pid;
        var specificPostScope = $('#p' + pid);
        if(specificPostScope.length) {
            if (!pendingUpdates.has(pid)) {
                pendingUpdates.add(pid);
                fetchVotesForPosts([pid]).always(() => {
                    pendingUpdates.delete(pid);
                });
            }
        }
    }
});
 
// Подписка на глобальное AJAX-событие для отслеживания лайков от любых пользователей
$(document).on('ajaxSuccess', function(e, xhr, data) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    if (data && data.url && /relation\.php.*[&?]v=(\d+)/.test(data.url)) {
        const match = data.url.match(/id=(\d+)/);
        const pid = match ? match[1] : null;
        const voteType = data.url.match(/v=(\d+)/)?.[1];
 
        if (pid && voteType === '1') {
            if (!pendingUpdates.has(pid)) {
                pendingUpdates.add(pid);
                fetchVotesForPosts([pid]).always(() => {
                    pendingUpdates.delete(pid);
                });
            }
        }
    }
});
 
// Наблюдатель за изменениями DOM для динамических постов
const observer = new MutationObserver(function(mutationsList) {
    // Работаем только на странице топика
    if (!$('#pun-viewtopic').length) return;
    
    let shouldUpdate = false;
    let relevantScope = document.body;
 
    for (let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            for (let node of mutation.addedNodes) {
                if (node.nodeType === 1) {
                    if (node.classList && node.classList.contains('post')) {
                        relevantScope = node;
                        shouldUpdate = true;
                        break;
                    } else if (node.querySelector && node.querySelector('div.post')) {
                        relevantScope = node;
                        shouldUpdate = true;
                        break;
                    } else {
                        const nestedPosts = node.querySelectorAll && node.querySelectorAll('div.post');
                        if (nestedPosts && nestedPosts.length > 0) {
                            relevantScope = node;
                            shouldUpdate = true;
                            break;
                        }
                    }
                }
            }
            if (shouldUpdate) break;
        }
    }
 
    if (shouldUpdate) {
        requestAnimationFrame(() => {
            applyPlusHandlers(relevantScope);
            hideAllNewPostVotes(relevantScope);
            scheduleUpdate(relevantScope);
        });
    }
});
 
observer.observe(document.body, { childList: true, subtree: true });
 
// Резервный таймер для обновления раз в секунду
const backupInterval = setInterval(combinedBackupUpdate, 1000);
</script>
Список изменений:

Добавлена проверка if (!$('#pun-viewtopic').length) return; в следующие места:

    ✅ getPostIds() - не получает ID постов вне топика
    ✅ setPlus() - не обрабатывает клики вне топика 
    ✅ BR() - обёртка для кликов
    ✅ hideAllNewPostVotes() - не скрывает элементы вне топика
    ✅ applyPlusHandlers() - не назначает обработчики вне топика
    ✅ insertThanksList() - не вставляет списки вне топика
    ✅ processVotes() - не обрабатывает ответы вне топика
    ✅ fetchVotesForPosts() - не делает запросы вне топика
    ✅ scheduleUpdate() - не планирует обновления вне топика
    ✅ combinedBackupUpdate() - не обновляет вне топика
    ✅ $(function() {...}) - не инициализирует вне топика
    ✅ document.addEventListener('pun_post', ...) - не реагирует на события вне топика
    ✅ document.addEventListener('vote:happened', ...) - не обрабатывает лайки вне топика
    ✅ $(document).on('ajaxSuccess', ...) - не слушает AJAX вне топика
    ✅ MutationObserver callback - не наблюдает за изменениями вне топика

+1

1746

@Alex_63
Смотри
есть твой мгновенный предпросмотр сообщений десятилетней давности
есть сервисные пользовательские теги
Как можно догадаться, из-за локального парса и конкретно того, что локальный парс ничего не знает про кастомные теги, указанные для конкретного форума, этот предпросмотр парсить "пользовательские теги" не может
Человеки отчаянно хотят именно мгновенный предпросмотр, который свитчится после каждого введенного символа, т.е. если парсить это на стороне севриса - это прям больше 1 рпс с тела может быть, и это прям не очень хорошо выглядит, я на 99.9% уверена, что это будет отбиваться рпс-лимитером.
В связи с этим, вопрос:
есть ли опция каким-либо образом вытащить функцию, аналогично сервисному механизму выпаршивающую пользовательские теги, или выход один - страдать юзать старую скриптовую реализацию того, что можно реализовать сервисными тегами, просто ради того, чтобы оно парсилось на клиенте?
Я не хочу даже пытаться написать с нуля то, что реализовано на стороне сервиса, да)))
Отдельный мем в том, что как будто есть желание условно шаблонные приколы запихать в пользовательские теги, чтобы не лепить html в каждом посте, но для каждого такого тега дорабатывать парс в мгновенном предпросмотре я явным образом не хочу.

Отредактировано Амираль (Вс, 1 Фев 2026 00:47:41)

0

1747

Амираль
Сразу скажу, что мгновенный предпросмотр точно не планируется к доработке, вместо него предлагается использовать визуальный редактор, в него можно и пользовательские теги добавить, по типу

Код:
WYSI.extend({
    'indent': ['.custom_tag_indent', '[indent]', ''],
    'float': ['.custom_tag_float', '[float]', '[/float]', function(startTag,elem) {
        return BBQuote.addAttribute(startTag, elem.attr('alt'))
    }]
});

С функцией из парсера в движке сложнее, я посмотрю на досуге что можно сделать. Сразу подумалось, что как минимум придётся ещё раз те же теги прописывать, что и в поле Пользовательские BB-теги, только уже передавать их в эту функцию. Как я понимаю, у тебя идея в том чтобы просто перевести с php на js то, что в парсере по части пользовательских тегов.

+1

1748

Alex_63 написал(а):

Амираль
Сразу скажу, что мгновенный предпросмотр точно не планируется к доработке, вместо него предлагается использовать визуальный редактор, в него можно и пользовательские теги добавить, по типу

Код:
WYSI.extend({
    'indent': ['.custom_tag_indent', '[indent]', ''],
    'float': ['.custom_tag_float', '[float]', '[/float]', function(startTag,elem) {
        return BBQuote.addAttribute(startTag, elem.attr('alt'))
    }]
});

С функцией из парсера в движке сложнее, я посмотрю на досуге что можно сделать. Сразу подумалось, что как минимум придётся ещё раз те же теги прописывать, что и в поле Пользовательские BB-теги, только уже передавать их в эту функцию. Как я понимаю, у тебя идея в том чтобы просто перевести с php на js то, что в парсере по части пользовательских тегов.

С идеей ты прав, да
Про визуальный идею закину)

+1


Вы здесь » Единый форум поддержки » Новые возможности форумов » Запросы по скриптам #11