Список пользователей

1
Админ
Постов: 232
2
VIP
Постов: 72
3
Элита
Постов: 51
4
Проверенные
Постов: 37
5
VIP
Постов: 35
6
Проверенные
Постов: 32
7
Пользователи
Постов: 31
8
Проверенные
Постов: 29

  • Страница 1 из 1
  • 1
Модуль ВК «Блок друзей» для Ucoz v 15.2 c подгрузкой
Дата: Вторник, 31.03.2026, 18:48 | Сообщение # 1 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:232
         Регистрация:20.10.2016

Эта версия получилась более интересной у меня появился дополнительный азарт добавлять в неё новые полезные функции.

Была реализована кнопка «подгрузка друзей», которая ограничивает загрузку данных: теперь отображаются только 6 друзей, а остальная информация скрывается. На мой взгляд, это полезная и практичная функция, так как она снижает нагрузку и делает интерфейс более аккуратным.

Скрипт построен по секционной архитектуре, благодаря чему его легко редактировать и расширять новыми возможностями. Переписывать всю структуру не требуется , система изначально спроектирована так, чтобы быть максимально автономной и самодостаточной.

В этот раз я планирую опубликовать две версии модуля: одна с повышенными приоритетами, где большая часть элементов скрыта, и другая более открытая, где отображаются некоторые дополнительные элементы.



Исходной код:

Код
<!-- MINI BLOCK ДРУЗЬЯ -->
<div class="vk-old-block">
<div class="vk-old-header">Друзья</div>
<div class="vk-old-footer" id="friends-count">0 друзей</div>

<div class="vk-old-content">
<div class="vk-friends"></div>
</div>

<div class="vk-old-footer" id="friends-footer" title="Показать всех друзей">
Показать всех друзей →
</div>
</div>

<link rel="stylesheet" href="/css/friends03.css" media="all">

<div id="uc-online-list" style="display:none;">
$ONLINE_USERS_LIST$
</div>

<script>
(function(){
const USER_ID = window.UCOZ_DATA?.userId || "0";
if(USER_ID === "0") return;

const $container = $('.vk-friends');
const $counter = $('#friends-count');
const $footer = $('#friends-footer');
const DEFAULT_AVA = '/.s/src/profile/img/profile_photo_thumbnail.png';
const LS_FRIENDS = 'friends_' + USER_ID;
let allFriends = [];

/* =========================
РЕНДЕР МИНИ-БЛОКА
========================= */
function renderMini(friends){
const onlineHtml = $('#uc-online-list').text().toLowerCase();
if(!friends || friends.length === 0){
const empty = document.createElement('div');
empty.className = 'vk-empty-wrap';
empty.innerHTML = `
<div class="vk-empty-icon">
<svg width="58" height="58" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 11c1.66 0 3-1.34 3-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zM8 11c1.66 0 3-1.34 3-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3z" fill="#99a2ad"/>
<path d="M8 13c-2.67 0-8 1.34-8 4v2h10v-2c0-1.2.6-2.3 1.6-3.2C10.3 13.3 9 13 8 13zm8 0c-.9 0-2.3.2-3.6.8 1 .9 1.6 2 1.6 3.2v2h10v-2c0-2.66-5.33-4-8-4z" fill="#99a2ad"/>
</svg>
</div>
<div class="vk-empty-title">У вас нет друзей</div>
<div class="vk-empty-sub">Добавляйте людей, чтобы видеть их здесь</div>
<div class="vk-empty-action">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Добавить друзей
</div>
`;
empty.querySelector('.vk-empty-action').onclick = () => location.href = '/index/15-2';
$container[0].replaceChildren(empty);
$counter.html('0 друзей');
return;
}

$container.empty();
friends.slice(0, 6).forEach(f => {
const isOnline = onlineHtml.includes(f.nick.toLowerCase());
const statusDot = `<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>`;
const card = $(`
<div class="vk-friend" title="${f.nick}">
<img src="${f.ava}">
<span>${f.nick}</span>
${statusDot}
</div>
`);
card.on('click', () => location.href = f.profile);
$container.append(card);
});
$counter.html(`${friends.length} друзей`);
}

/* =========================
POPUP
========================= */

function showPopup(){
    if(!allFriends.length) return;

    $('.vk-ui-overlay').remove();

    let visibleCount = 6;

    const overlay = $('<div class="vk-ui-overlay"></div>');
    const popup = $(`
        <div class="vk-ui-popup">
            <div class="vk-ui-header">
                <div class="vk-ui-title">
                    Друзья <span class="vk-ui-title-count">${allFriends.length}</span>
                </div>
                <div class="vk-ui-search">
                    <input type="text" placeholder="Поиск друзей" id="friend-search">
                </div>
                <button class="vk-ui-close">×</button>
            </div>

            <div class="vk-ui-list"></div>
            <div class="vk-ui-more-wrap"></div>
        </div>
    `);

    const list = popup.find('.vk-ui-list');
    const moreWrap = popup.find('.vk-ui-more-wrap');
    const onlineHtml = $('#uc-online-list').text().toLowerCase();

    function renderList(filter = ''){
        list.empty();
        moreWrap.empty();

        const filtered = allFriends.filter(f =>
            f.nick.toLowerCase().includes(filter.toLowerCase())
        );

        filtered.slice(0, visibleCount).forEach(f => {

            let badge = 'Без статуса', cls = 'nostatus';
            const g = (f.group || '').toLowerCase();

            if (g.includes('кумир')) { badge = 'Кумир'; cls = 'idol'; }
            else if (g.includes('друг')) { badge = 'Друг'; cls = 'friend'; }
            else if (g.includes('сем')) { badge = 'Семья'; cls = 'family'; }
            else if (g.includes('знаком')) { badge = 'Знакомый'; cls = 'acquaintance'; }
            else if (g.includes('прият')) { badge = 'Приятель'; cls = 'buddy'; }
            else if (g.includes('коллег')) { badge = 'Коллега'; cls = 'colleague'; }

            const isOnline = onlineHtml.includes(f.nick.toLowerCase());

            const row = $(`
                <div class="vk-ui-row ${cls}">
                    <div class="vk-ui-ava">
                        <img src="${f.ava}">
                        <span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>
                    </div>

                    <div class="vk-ui-info">
                        <div class="vk-ui-name">${f.nick}</div>
                        <div class="vk-ui-badge ${cls}">${badge}</div>
                    </div>

                    <div class="vk-ui-actions">
                        <button class="vk-del-btn">✕</button>
                        <div class="vk-ui-arrow">›</div>
                    </div>
                </div>
            `);

            // переход в профиль
            row.on('click', function(e){
                if(!$(e.target).closest('.vk-del-btn').length){
                    location.href = f.profile;
                }
            });

            // удаление
            row.find('.vk-del-btn').on('click', function(e){
                e.stopPropagation();

                const btn = $(this);

                if(btn.data('loading')) return;
                if(!f.del) return alert('Нет ссылки удаления');

                openDeleteModal(f.nick, f.ava, () => {

                    btn.data('loading', true);
                    btn.text('...').css({opacity:0.6, pointerEvents:'none'});

                    $.get(f.del).done(() => {

                        allFriends = allFriends.filter(x => x.nick !== f.nick);

                        row.css({
                            transition: '0.45s cubic-bezier(.2,.9,.2,1)',
                            transform: 'translateX(120px) scale(0.3) rotate(20deg)',
                            opacity: '0',
                            filter: 'blur(8px)'
                        });

                        setTimeout(() => row.remove(), 400);

                        renderMini(allFriends);
                        $counter.html(`${allFriends.length} друзей`);
                        localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));
                    });
                });
            });

            list.append(row);
        });

        if (visibleCount < filtered.length || (maxPage && currentPage < maxPage)) {

            const btn = $('<div class="vk-more-btn">Show more</div>');

            btn.on('click', function () {

                const $btn = $(this);
                if ($btn.data('loading')) return;

                $btn.data('loading', true);
                $btn.addClass('loading');
                $btn.html(`<span class="spinner"></span>Загрузка...`);

                list.append(`
                    <div class="vk-loading-box">
                        <div class="vk-loading-spinner"></div>
                        <div class="vk-loading-title">Загружаем друзей</div>
                        <div class="vk-loading-sub">Идёт обновление данных...</div>
                    </div>
                `);

                if (visibleCount < filtered.length) {

                    setTimeout(() => {
                        $('.vk-loading-box').remove();
                        visibleCount += 6;
                        renderList(filter);
                        $btn.data('loading', false);
                    }, 450);

                    return;
                }

                loadMoreFriends();

                setTimeout(() => {
                    $('.vk-loading-box').remove();
                    visibleCount += 6;
                    renderList(filter);
                    $btn.data('loading', false);
                }, 800);
            });

            moreWrap.append(btn);
        }
    }

    renderList();

    popup.find('#friend-search').on('input', function(){
        visibleCount = 6;
        renderList($(this).val());
    });

    popup.find('.vk-ui-close').on('click', () => overlay.remove());
    overlay.on('click', e => {
        if(e.target === overlay[0]) overlay.remove();
    });

    //  ВАЖНО: правильный порядок (как у тебя в старом рабочем коде)
    overlay.css({
        position: 'fixed',
        inset: 0,
        zIndex: 2147483647
    });

    popup.css({
        position: 'relative',
        zIndex: 2147483647
    });

    overlay.append(popup);
    $('body').append(overlay);
}

/* =========================
МОДАЛКА УДАЛЕНИЯ
========================= */
function ensureModal(){
if ($('.vk-modal-overlay').length) return;
$('body').append(`
<div class="vk-modal-overlay">
<div class="vk-modal">
<div class="vk-modal-user">
<div class="vk-modal-avatar"><img src="" alt=""></div>
<div>
<div class="vk-modal-user-name"></div>
<div class="vk-modal-user-sub">будет удалён из друзей</div>
</div>
</div>
<div class="vk-modal-title">Удаление друга</div>
<div class="vk-modal-text"></div>
<div class="vk-modal-warning">Это действие нельзя отменить</div>
<div class="vk-modal-actions">
<button class="vk-modal-cancel">Отмена</button>
<button class="vk-modal-confirm">Удалить</button>
</div>
</div>
</div>
`);
}

function openDeleteModal(name, ava, onConfirm){
ensureModal();
const modal = $('.vk-modal-overlay');
modal.find('.vk-modal-text').text(`Удалить "${name}" из друзей?`);
modal.find('.vk-modal-avatar img').attr('src', ava || DEFAULT_AVA);
modal.find('.vk-modal-user-name').text(name);

modal.addClass('active');
$('body').addClass('vk-modal-open');

function closeModal(){
modal.removeClass('active');
$('body').removeClass('vk-modal-open');
}

let locked = false;

modal.find('.vk-modal-cancel').off('click').on('click', () => { if(!locked) closeModal(); });

modal.find('.vk-modal-confirm').off('click').on('click', function(){
if(locked) return;
locked = true;
const btn = $(this);
btn.text('Удаление...').css({'pointer-events':'none', 'opacity':'0.7'});

setTimeout(() => {
closeModal();
if(typeof onConfirm === 'function') onConfirm();
btn.text('Удалить').css({'pointer-events':'auto', 'opacity':'1'});
locked = false;
}, 180);
});

modal.off('click').on('click', e => { if(e.target === this && !locked) closeModal(); });
$(document).off('keydown.vkmodal').on('keydown.vkmodal', e => { if(e.key === 'Escape' && !locked) closeModal(); });
}

/* =========================
ЗАГРУЗКА ДАННЫХ — ПОЛНАЯ ВЕРСИЯ (с пагинацией)
========================= */

let currentPage = 1;
let maxPage = null;
let isLoading = false;

function loadFriends({ append = false, silent = false } = {}) {
    if (isLoading) return;
    isLoading = true;

    if (!silent) $("body").css("cursor", "wait");

    $.get(`/blog/0-0-${currentPage}-0-17-${USER_ID}?${Math.random()}`, html => {
        const $tmp = $('<div>').html(html);
        let loaded = [];

        // определяем количество страниц
        if ($tmp.find("#pagesBlock1").length && currentPage === 1) {
            const lastPage = $tmp.find("a.swchItem").eq(-2).text();
            maxPage = parseInt(lastPage) || null;
        }

        $tmp.find('.friend').each(function(){
            const $el = $(this);

            let group = $el.find('.gr').text().trim();

            if (!group) {
                const text = $el.text();
                const match = text.match(/(Кумир|Друг|Семья|Приятель|Знакомый|Коллега)/i);
                group = match ? match[0] : 'Без статуса';
            }

            loaded.push({
                nick: $el.find('.nick').text().trim(),
                ava: $el.find('.ava').text().trim() || DEFAULT_AVA,
                profile: $el.find('.url').text().trim(),
                group: group,
                del: $el.find('.del').text().trim() || null
            });
        });

        if (append) {
            const existing = new Set(allFriends.map(f => f.nick));
            loaded = loaded.filter(f => !existing.has(f.nick));
            allFriends = allFriends.concat(loaded);
        } else {
            allFriends = loaded;
        }

        localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));

        if (!silent) {
            renderMini(allFriends);
        }

    }).fail(() => {
        console.warn('Не удалось загрузить список друзей');
    }).always(() => {
        isLoading = false;
        $("body").css("cursor", "default");
    });
}

/* =========================
ДОГРУЗКА (как в VK)
========================= */
function loadMoreFriends() {
    if (isLoading) return;
    if (maxPage && currentPage >= maxPage) return;

    currentPage++;
    loadFriends({ append: true });
}

/* =========================
ИНИЦИАЛИЗАЦИЯ
========================= */

// открытие popup
$counter.add($footer)
    .css('cursor','pointer')
    .attr('title','Показать всех друзей')
    .on('click', showPopup);

// загрузка из кеша
try {
    const cached = JSON.parse(localStorage.getItem(LS_FRIENDS) || "[]");
    allFriends = cached;
    renderMini(allFriends);
} catch(e){
    renderMini([]);
}

// первая загрузка
setTimeout(() => {
    currentPage = 1;
    loadFriends();
}, 800);

// автообновление (тихо)
setInterval(() => {
    currentPage = 1;
    loadFriends({ silent: true });
}, 30000);

})();
</script>


Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Вторник, 31.03.2026, 18:50 | Сообщение # 2 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:232
         Регистрация:20.10.2016



Код
<!-- MINI BLOCK ДРУЗЬЯ -->
<div class="vk-old-block">
<div class="vk-old-header">Друзья</div>
<div class="vk-old-footer" id="friends-count">0 друзей</div>

<div class="vk-old-content">
<div class="vk-friends"></div>
</div>

<div class="vk-old-footer" id="friends-footer" title="Показать всех друзей">
Показать всех друзей →
</div>
</div>

<link rel="stylesheet" href="/css/friends03.css" media="all">

<div id="uc-online-list" style="display:none;">
$ONLINE_USERS_LIST$
</div>

<script>
(function(){
const USER_ID = window.UCOZ_DATA?.userId || "0";
if(USER_ID === "0") return;

const $container = $('.vk-friends');
const $counter = $('#friends-count');
const $footer = $('#friends-footer');
const DEFAULT_AVA = '/.s/src/profile/img/profile_photo_thumbnail.png';
const LS_FRIENDS = 'friends_' + USER_ID;
let allFriends = [];

/* =========================
РЕНДЕР МИНИ-БЛОКА
========================= */
function renderMini(friends){
const onlineHtml = $('#uc-online-list').text().toLowerCase();
if(!friends || friends.length === 0){
const empty = document.createElement('div');
empty.className = 'vk-empty-wrap';
empty.innerHTML = `
<div class="vk-empty-icon">
<svg width="58" height="58" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 11c1.66 0 3-1.34 3-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zM8 11c1.66 0 3-1.34 3-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3z" fill="#99a2ad"/>
<path d="M8 13c-2.67 0-8 1.34-8 4v2h10v-2c0-1.2.6-2.3 1.6-3.2C10.3 13.3 9 13 8 13zm8 0c-.9 0-2.3.2-3.6.8 1 .9 1.6 2 1.6 3.2v2h10v-2c0-2.66-5.33-4-8-4z" fill="#99a2ad"/>
</svg>
</div>
<div class="vk-empty-title">У вас нет друзей</div>
<div class="vk-empty-sub">Добавляйте людей, чтобы видеть их здесь</div>
<div class="vk-empty-action">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Добавить друзей
</div>
`;
empty.querySelector('.vk-empty-action').onclick = () => location.href = '/index/15-2';
$container[0].replaceChildren(empty);
$counter.html('0 друзей');
return;
}

$container.empty();
friends.slice(0, 6).forEach(f => {
const isOnline = onlineHtml.includes(f.nick.toLowerCase());
const statusDot = `<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>`;
const card = $(`
<div class="vk-friend" title="${f.nick}">
<img src="${f.ava}">
<span>${f.nick}</span>
${statusDot}
</div>
`);
card.on('click', () => location.href = f.profile);
$container.append(card);
});
$counter.html(`${friends.length} друзей`);
}

/* =========================
POPUP
========================= */
function showPopup(){
    if(!allFriends.length) return;

    $('.vk-ui-overlay').remove();

    let visibleCount = 6;

    const overlay = $('<div class="vk-ui-overlay"></div>');
    const popup = $(`
    <div class="vk-ui-popup">
        <div class="vk-ui-header">
            <div class="vk-ui-title">
                Друзья <span class="vk-ui-title-count">${allFriends.length}</span>
            </div>
            <div class="vk-ui-search">
                <input type="text" placeholder="Поиск друзей" id="friend-search">
            </div>
            <button class="vk-ui-close">×</button>
        </div>

        <div class="vk-ui-list"></div>
        <div class="vk-ui-more-wrap"></div>
    </div>
    `);

    const list = popup.find('.vk-ui-list');
    const moreWrap = popup.find('.vk-ui-more-wrap');

    const onlineHtml = $('#uc-online-list').text().toLowerCase();

    function renderList(filter = ''){
        list.empty();
        moreWrap.empty();

        const filtered = allFriends.filter(f =>
            f.nick.toLowerCase().includes(filter.toLowerCase())
        );

        filtered.slice(0, visibleCount).forEach(f => {

            let badge = 'Без статуса', cls = 'nostatus';

            const g = (f.group || '').toLowerCase();

            if (g.includes('кумир')) { badge = 'Кумир'; cls = 'idol'; }
            else if (g.includes('друг')) { badge = 'Друг'; cls = 'friend'; }
            else if (g.includes('сем')) { badge = 'Семья'; cls = 'family'; }
            else if (g.includes('знаком')) { badge = 'Знакомый'; cls = 'acquaintance'; }
            else if (g.includes('прият')) { badge = 'Приятель'; cls = 'buddy'; }
            else if (g.includes('коллег')) { badge = 'Коллега'; cls = 'colleague'; }

            const isOnline = onlineHtml.includes(f.nick.toLowerCase());

const row = $(`
<div class="vk-ui-row ${cls}">
    <div class="vk-ui-ava">
        <img src="${f.ava}">
        <span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>
    </div>

    <div class="vk-ui-info">
        <div class="vk-ui-name">${f.nick}</div>
        <div class="vk-ui-badge ${cls}">${badge}</div>
    </div>

    <div class="vk-ui-actions">
        <button class="vk-del-btn">✕</button>
        <div class="vk-ui-arrow">›</div>
    </div>
</div>
`);

// переход в профиль (НЕ мешает кнопке удаления)
row.on('click', function(e){
    if(!$(e.target).closest('.vk-del-btn').length) {
        location.href = f.profile;
    }
});

// УДАЛЕНИЕ ДРУГА
row.find('.vk-del-btn').on('click', function(e){
    e.stopPropagation();

    const btn = $(this);

    if(btn.data('loading')) return;
    if(!f.del) return alert('Нет ссылки удаления');

    openDeleteModal(f.nick, f.ava, () => {

        btn.data('loading', true);
        btn.text('...').css({'opacity':'0.6','pointer-events':'none'});

        $.get(f.del).done(() => {

            allFriends = allFriends.filter(x => x.nick !== f.nick);

            row.css({
                transition: '0.45s cubic-bezier(.2,.9,.2,1)',
                transform: 'translateX(120px) scale(0.3) rotate(20deg)',
                opacity: '0',
                filter: 'blur(8px)'
            });

            setTimeout(() => row.remove(), 400);

            renderMini(allFriends);
            $counter.html(`${allFriends.length} друзей`);
            localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));
        });
    });
});

list.append(row);

});

        //  КНОПКА
        if (visibleCount < filtered.length || (maxPage && currentPage < maxPage)) {
            const btn = $(`<div class="vk-more-btn">Show more </div>`);

btn.on('click', function () {

    const $btn = $(this);

    if ($btn.data('loading')) return;

    $btn.data('loading', true);

    //  моментальный эффект нажатия
    $btn.addClass('clicked');

    setTimeout(() => $btn.removeClass('clicked'), 150);

    //  блокируем кнопку визуально
    $btn.addClass('loading');
    $btn.html(`
        <span class="spinner"></span>
        Загрузка...
    `);

    //  ВАЖНО: показываем ОТДЕЛЬНЫЙ loading-блок
    list.append(`
        <div class="vk-loading-box">
            <div class="vk-loading-spinner"></div>
            <div class="vk-loading-title">Загружаем друзей</div>
            <div class="vk-loading-sub">Идёт обновление данных...</div>
        </div>
    `);

    // =========================
    // 1. просто раскрываем список
    // =========================
    if (visibleCount < filtered.length) {

        setTimeout(() => {

            $('.vk-loading-box').remove();

            visibleCount += 6;
            renderList(filter);

            $btn.data('loading', false);

        }, 450);

        return;
    }

    // =========================
    // 2. грузим с сервера
    // =========================
    loadMoreFriends();

    setTimeout(() => {

        $('.vk-loading-box').remove();

        visibleCount += 6;
        renderList(filter);

        $btn.data('loading', false);

    }, 800);
});

            moreWrap.append(btn);
        }
    }

    renderList();

    popup.find('#friend-search').on('input', function(){
        visibleCount = 6;
        renderList($(this).val());
    });

    popup.find('.vk-ui-close').on('click', () => overlay.remove());
    overlay.on('click', e => { if(e.target === overlay[0]) overlay.remove(); });

    overlay.append(popup);
    $('body').append(overlay);
}

/* =========================
МОДАЛКА УДАЛЕНИЯ
========================= */
function ensureModal(){
if ($('.vk-modal-overlay').length) return;
$('body').append(`
<div class="vk-modal-overlay">
<div class="vk-modal">
<div class="vk-modal-user">
<div class="vk-modal-avatar"><img src="" alt=""></div>
<div>
<div class="vk-modal-user-name"></div>
<div class="vk-modal-user-sub">будет удалён из друзей</div>
</div>
</div>
<div class="vk-modal-title">Удаление друга</div>
<div class="vk-modal-text"></div>
<div class="vk-modal-warning">Это действие нельзя отменить</div>
<div class="vk-modal-actions">
<button class="vk-modal-cancel">Отмена</button>
<button class="vk-modal-confirm">Удалить</button>
</div>
</div>
</div>
`);
}

function openDeleteModal(name, ava, onConfirm){
ensureModal();
const modal = $('.vk-modal-overlay');
modal.find('.vk-modal-text').text(`Удалить "${name}" из друзей?`);
modal.find('.vk-modal-avatar img').attr('src', ava || DEFAULT_AVA);
modal.find('.vk-modal-user-name').text(name);

modal.addClass('active');
$('body').addClass('vk-modal-open');

function closeModal(){
modal.removeClass('active');
$('body').removeClass('vk-modal-open');
}

let locked = false;

modal.find('.vk-modal-cancel').off('click').on('click', () => { if(!locked) closeModal(); });

modal.find('.vk-modal-confirm').off('click').on('click', function(){
if(locked) return;
locked = true;
const btn = $(this);
btn.text('Удаление...').css({'pointer-events':'none', 'opacity':'0.7'});

setTimeout(() => {
closeModal();
if(typeof onConfirm === 'function') onConfirm();
btn.text('Удалить').css({'pointer-events':'auto', 'opacity':'1'});
locked = false;
}, 180);
});

modal.off('click').on('click', e => { if(e.target === this && !locked) closeModal(); });
$(document).off('keydown.vkmodal').on('keydown.vkmodal', e => { if(e.key === 'Escape' && !locked) closeModal(); });
}

/* =========================
ЗАГРУЗКА ДАННЫХ — ПОЛНАЯ ВЕРСИЯ (с пагинацией)
========================= */

let currentPage = 1;
let maxPage = null;
let isLoading = false;

function loadFriends({ append = false, silent = false } = {}) {
    if (isLoading) return;
    isLoading = true;

    if (!silent) $("body").css("cursor", "wait");

    $.get(`/blog/0-0-${currentPage}-0-17-${USER_ID}?${Math.random()}`, html => {
        const $tmp = $('<div>').html(html);
        let loaded = [];

        // определяем количество страниц
        if ($tmp.find("#pagesBlock1").length && currentPage === 1) {
            const lastPage = $tmp.find("a.swchItem").eq(-2).text();
            maxPage = parseInt(lastPage) || null;
        }

        $tmp.find('.friend').each(function(){
            const $el = $(this);

            let group = $el.find('.gr').text().trim();

            if (!group) {
                const text = $el.text();
                const match = text.match(/(Кумир|Друг|Семья|Приятель|Знакомый|Коллега)/i);
                group = match ? match[0] : 'Без статуса';
            }

            loaded.push({
                nick: $el.find('.nick').text().trim(),
                ava: $el.find('.ava').text().trim() || DEFAULT_AVA,
                profile: $el.find('.url').text().trim(),
                group: group,
                del: $el.find('.del').text().trim() || null
            });
        });

        if (append) {
            const existing = new Set(allFriends.map(f => f.nick));
            loaded = loaded.filter(f => !existing.has(f.nick));
            allFriends = allFriends.concat(loaded);
        } else {
            allFriends = loaded;
        }

        localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));

        if (!silent) {
            renderMini(allFriends);
        }

    }).fail(() => {
        console.warn('Не удалось загрузить список друзей');
    }).always(() => {
        isLoading = false;
        $("body").css("cursor", "default");
    });
}

/* =========================
ДОГРУЗКА (как в VK)
========================= */
function loadMoreFriends() {
    if (isLoading) return;
    if (maxPage && currentPage >= maxPage) return;

    currentPage++;
    loadFriends({ append: true });
}

/* =========================
ИНИЦИАЛИЗАЦИЯ
========================= */

// открытие popup
$counter.add($footer)
    .css('cursor','pointer')
    .attr('title','Показать всех друзей')
    .on('click', showPopup);

// загрузка из кеша
try {
    const cached = JSON.parse(localStorage.getItem(LS_FRIENDS) || "[]");
    allFriends = cached;
    renderMini(allFriends);
} catch(e){
    renderMini([]);
}

// первая загрузка
setTimeout(() => {
    currentPage = 1;
    loadFriends();
}, 800);

// автообновление (тихо)
setInterval(() => {
    currentPage = 1;
    loadFriends({ silent: true });
}, 30000);

})();
</script>

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Вторник, 31.03.2026, 19:19 | Сообщение # 3 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:232
         Регистрация:20.10.2016

Ещё одна версия



Исходник:

Код
<!-- MINI BLOCK ДРУЗЬЯ -->
<div class="vk-old-block">
<div class="vk-old-header">Друзья</div>
<div class="vk-old-footer" id="friends-count">0 друзей</div>

<div class="vk-old-content">
<div class="vk-friends"></div>
</div>

<div class="vk-old-footer" id="friends-footer" title="Показать всех друзей">
Показать всех друзей →
</div>
</div>

<link rel="stylesheet" href="/css/friends03.css" media="all">

<div id="uc-online-list" style="display:none;">
$ONLINE_USERS_LIST$
</div>

<script>
(function(){
const USER_ID = window.UCOZ_DATA?.userId || "0";
if(USER_ID === "0") return;

const $container = $('.vk-friends');
const $counter = $('#friends-count');
const $footer = $('#friends-footer');
const DEFAULT_AVA = '/.s/src/profile/img/profile_photo_thumbnail.png';
const LS_FRIENDS = 'friends_' + USER_ID;
let allFriends = [];

/* =========================
РЕНДЕР МИНИ-БЛОКА
========================= */
function renderMini(friends){
const onlineHtml = $('#uc-online-list').text().toLowerCase();
if(!friends || friends.length === 0){
const empty = document.createElement('div');
empty.className = 'vk-empty-wrap';
empty.innerHTML = `
<div class="vk-empty-icon">
<svg width="58" height="58" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 11c1.66 0 3-1.34 3-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zM8 11c1.66 0 3-1.34 3-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3z" fill="#99a2ad"/>
<path d="M8 13c-2.67 0-8 1.34-8 4v2h10v-2c0-1.2.6-2.3 1.6-3.2C10.3 13.3 9 13 8 13zm8 0c-.9 0-2.3.2-3.6.8 1 .9 1.6 2 1.6 3.2v2h10v-2c0-2.66-5.33-4-8-4z" fill="#99a2ad"/>
</svg>
</div>
<div class="vk-empty-title">У вас нет друзей</div>
<div class="vk-empty-sub">Добавляйте людей, чтобы видеть их здесь</div>
<div class="vk-empty-action">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Добавить друзей
</div>
`;
empty.querySelector('.vk-empty-action').onclick = () => location.href = '/index/15-2';
$container[0].replaceChildren(empty);
$counter.html('0 друзей');
return;
}

$container.empty();
friends.slice(0, 6).forEach(f => {
const isOnline = onlineHtml.includes(f.nick.toLowerCase());
const statusDot = `<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>`;
const card = $(`
<div class="vk-friend" title="${f.nick}">
<img src="${f.ava}">
<span>${f.nick}</span>
${statusDot}
</div>
`);
card.on('click', () => location.href = f.profile);
$container.append(card);
});
$counter.html(`${friends.length} друзей`);
}

/* =========================
POPUP
========================= */
function showPopup(){
    if(!allFriends.length) return;

    $('.vk-ui-overlay').remove();

    let visibleCount = 6;
    let isLoadingMore = false;
    let lastFilter = '';

    const overlay = $('<div class="vk-ui-overlay"></div>');

    const popup = $(`
        <div class="vk-ui-popup">
            <div class="vk-ui-header">
                <div class="vk-ui-title">
                    Друзья <span class="vk-ui-title-count">${allFriends.length}</span>
                </div>

                <div class="vk-ui-search">
                    <input type="text" placeholder="Поиск друзей" id="friend-search">
                </div>

                <button class="vk-ui-close">×</button>
            </div>

            <div class="vk-ui-list"></div>
            <div class="vk-ui-more-wrap"></div>
        </div>
    `);

    const list = popup.find('.vk-ui-list');
    const moreWrap = popup.find('.vk-ui-more-wrap');
    const onlineHtml = $('#uc-online-list').text().toLowerCase();

    function getStatus(f){
        let badge = 'Без статуса';
        let cls = 'nostatus';
        let letter = '';

        const g = (f.group || '').toLowerCase();

        if (g.includes('кумир')) { badge = 'Кумир'; cls = 'idol'; letter = 'К'; }
        else if (g.includes('друг')) { badge = 'Друг'; cls = 'friend'; letter = 'Д'; }
        else if (g.includes('сем')) { badge = 'Семья'; cls = 'family'; letter = 'С'; }
        else if (g.includes('знаком')) { badge = 'Знакомый'; cls = 'acquaintance'; letter = 'З'; }
        else if (g.includes('прият')) { badge = 'Приятель'; cls = 'buddy'; letter = 'П'; }
        else if (g.includes('коллег')) { badge = 'Коллега'; cls = 'colleague'; letter = 'К'; }

        return { badge, cls, letter };
    }

    function renderList(filter = ''){
        list.empty();
        moreWrap.empty();

        lastFilter = filter;

        const filtered = allFriends.filter(f =>
            (f.nick || '').toLowerCase().includes(filter.toLowerCase())
        );

        const slice = filtered.slice(0, visibleCount);

        slice.forEach(f => {

            const { badge, cls, letter } = getStatus(f);
            const isOnline = onlineHtml.includes((f.nick || '').toLowerCase());

            const row = $(`
                <div class="vk-ui-row ${cls}">
                    <div class="vk-ui-ava">
                        <img src="${f.ava}">

                        ${letter ? `
                            <span class="vk-ui-letter ${cls}">${letter}</span>
                        ` : ''}

                        <span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>
                    </div>

                    <div class="vk-ui-info">
                        <div class="vk-ui-name">${f.nick}</div>
                        <div class="vk-ui-badge ${cls}">${badge}</div>
                    </div>

                    <div class="vk-ui-actions">
                        <button class="vk-del-btn">✕</button>
                        <div class="vk-ui-arrow">›</div>
                    </div>
                </div>
            `);

            // переход в профиль
            row.on('click', function(e){
                if(!$(e.target).closest('.vk-del-btn').length){
                    location.href = f.profile;
                }
            });

            // удаление
            row.find('.vk-del-btn').on('click', function(e){
                e.stopPropagation();

                const btn = $(this);

                if(btn.data('loading')) return;
                if(!f.del) return alert('Нет ссылки удаления');

                openDeleteModal(f.nick, f.ava, () => {

                    btn.data('loading', true);
                    btn.text('...').css({
                        opacity: 0.6,
                        pointerEvents: 'none'
                    });

                    $.get(f.del).done(() => {

                        allFriends = allFriends.filter(x => x.nick !== f.nick);

                        row.css({
                            transition: '0.45s cubic-bezier(.2,.9,.2,1)',
                            transform: 'translateX(120px) scale(0.3) rotate(20deg)',
                            opacity: '0',
                            filter: 'blur(8px)'
                        });

                        setTimeout(() => row.remove(), 400);

                        renderMini(allFriends);
                        $counter.html(`${allFriends.length} друзей`);
                        localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));

                        renderList(lastFilter);
                    });
                });
            });

            list.append(row);
        });

        //  КНОПКА
        if (visibleCount < filtered.length || (maxPage && currentPage < maxPage)) {
            const btn = $(`<div class="vk-more-btn">Show more </div>`);

btn.on('click', function () {

    const $btn = $(this);

    if ($btn.data('loading')) return;

    $btn.data('loading', true);

    //  моментальный эффект нажатия
    $btn.addClass('clicked');

    setTimeout(() => $btn.removeClass('clicked'), 150);

    //  блокируем кнопку визуально
    $btn.addClass('loading');
    $btn.html(`
        <span class="spinner"></span>
        Загрузка...
    `);

    //  ВАЖНО: показываем ОТДЕЛЬНЫЙ loading-блок
    list.append(`
        <div class="vk-loading-box">
            <div class="vk-loading-spinner"></div>
            <div class="vk-loading-title">Загружаем друзей</div>
            <div class="vk-loading-sub">Идёт обновление данных...</div>
        </div>
    `);

    // =========================
    // 1. просто раскрываем список
    // =========================
    if (visibleCount < filtered.length) {

        setTimeout(() => {

            $('.vk-loading-box').remove();

            visibleCount += 6;
            renderList(filter);

            $btn.data('loading', false);

        }, 450);

        return;
    }

    // =========================
    // 2. грузим с сервера
    // =========================
    loadMoreFriends();

    setTimeout(() => {

        $('.vk-loading-box').remove();

        visibleCount += 6;
        renderList(filter);

        $btn.data('loading', false);

    }, 800);
});

            moreWrap.append(btn);
        }
    }

    renderList();

    popup.find('#friend-search').on('input', function(){
        visibleCount = 6;
        renderList($(this).val());
    });

    popup.find('.vk-ui-close').on('click', () => overlay.remove());
    overlay.on('click', e => { if(e.target === overlay[0]) overlay.remove(); });

    overlay.append(popup);
    $('body').append(overlay);
}

/* =========================
МОДАЛКА УДАЛЕНИЯ
========================= */
function ensureModal(){
if ($('.vk-modal-overlay').length) return;
$('body').append(`
<div class="vk-modal-overlay">
<div class="vk-modal">
<div class="vk-modal-user">
<div class="vk-modal-avatar"><img src="" alt=""></div>
<div>
<div class="vk-modal-user-name"></div>
<div class="vk-modal-user-sub">будет удалён из друзей</div>
</div>
</div>
<div class="vk-modal-title">Удаление друга</div>
<div class="vk-modal-text"></div>
<div class="vk-modal-warning">Это действие нельзя отменить</div>
<div class="vk-modal-actions">
<button class="vk-modal-cancel">Отмена</button>
<button class="vk-modal-confirm">Удалить</button>
</div>
</div>
</div>
`);
}

function openDeleteModal(name, ava, onConfirm){
ensureModal();
const modal = $('.vk-modal-overlay');
modal.find('.vk-modal-text').text(`Удалить "${name}" из друзей?`);
modal.find('.vk-modal-avatar img').attr('src', ava || DEFAULT_AVA);
modal.find('.vk-modal-user-name').text(name);

modal.addClass('active');
$('body').addClass('vk-modal-open');

function closeModal(){
modal.removeClass('active');
$('body').removeClass('vk-modal-open');
}

let locked = false;

modal.find('.vk-modal-cancel').off('click').on('click', () => { if(!locked) closeModal(); });

modal.find('.vk-modal-confirm').off('click').on('click', function(){
if(locked) return;
locked = true;
const btn = $(this);
btn.text('Удаление...').css({'pointer-events':'none', 'opacity':'0.7'});

setTimeout(() => {
closeModal();
if(typeof onConfirm === 'function') onConfirm();
btn.text('Удалить').css({'pointer-events':'auto', 'opacity':'1'});
locked = false;
}, 180);
});

modal.off('click').on('click', e => { if(e.target === this && !locked) closeModal(); });
$(document).off('keydown.vkmodal').on('keydown.vkmodal', e => { if(e.key === 'Escape' && !locked) closeModal(); });
}

/* =========================
ЗАГРУЗКА ДАННЫХ — ПОЛНАЯ ВЕРСИЯ (с пагинацией)
========================= */

let currentPage = 1;
let maxPage = null;
let isLoading = false;

function loadFriends({ append = false, silent = false } = {}) {
    if (isLoading) return;
    isLoading = true;

    if (!silent) $("body").css("cursor", "wait");

    $.get(`/blog/0-0-${currentPage}-0-17-${USER_ID}?${Math.random()}`, html => {
        const $tmp = $('<div>').html(html);
        let loaded = [];

        // определяем количество страниц
        if ($tmp.find("#pagesBlock1").length && currentPage === 1) {
            const lastPage = $tmp.find("a.swchItem").eq(-2).text();
            maxPage = parseInt(lastPage) || null;
        }

        $tmp.find('.friend').each(function(){
            const $el = $(this);

            let group = $el.find('.gr').text().trim();

            if (!group) {
                const text = $el.text();
                const match = text.match(/(Кумир|Друг|Семья|Приятель|Знакомый|Коллега)/i);
                group = match ? match[0] : 'Без статуса';
            }

            loaded.push({
                nick: $el.find('.nick').text().trim(),
                ava: $el.find('.ava').text().trim() || DEFAULT_AVA,
                profile: $el.find('.url').text().trim(),
                group: group,
                del: $el.find('.del').text().trim() || null
            });
        });

        if (append) {
            const existing = new Set(allFriends.map(f => f.nick));
            loaded = loaded.filter(f => !existing.has(f.nick));
            allFriends = allFriends.concat(loaded);
        } else {
            allFriends = loaded;
        }

        localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));

        if (!silent) {
            renderMini(allFriends);
        }

    }).fail(() => {
        console.warn('Не удалось загрузить список друзей');
    }).always(() => {
        isLoading = false;
        $("body").css("cursor", "default");
    });
}

/* =========================
ДОГРУЗКА (как в VK)
========================= */
function loadMoreFriends() {
    if (isLoading) return;
    if (maxPage && currentPage >= maxPage) return;

    currentPage++;
    loadFriends({ append: true });
}

/* =========================
ИНИЦИАЛИЗАЦИЯ
========================= */

// открытие popup
$counter.add($footer)
    .css('cursor','pointer')
    .attr('title','Показать всех друзей')
    .on('click', showPopup);

// загрузка из кеша
try {
    const cached = JSON.parse(localStorage.getItem(LS_FRIENDS) || "[]");
    allFriends = cached;
    renderMini(allFriends);
} catch(e){
    renderMini([]);
}

// первая загрузка
setTimeout(() => {
    currentPage = 1;
    loadFriends();
}, 800);

// автообновление (тихо)
setInterval(() => {
    currentPage = 1;
    loadFriends({ silent: true });
}, 30000);

})();
</script>

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
  • Страница 1 из 1
  • 1
Поиск: