Модуль ВК «Блок друзей» для Ucoz v 15.2 c подгрузкой
Дата: Вторник, 31.03.2026, 18:48 | Сообщение # 1 |
|
Написал: Узнаваемый
Автор темы
Мурчанн
не в сети
Сообщений: 232
Эта версия получилась более интересной у меня появился дополнительный азарт добавлять в неё новые полезные функции. Была реализована кнопка «подгрузка друзей», которая ограничивает загрузку данных: теперь отображаются только 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
Код
<!-- 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
Ещё одна версия Исходник: Код
<!-- 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>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.