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

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

  • Страница 1 из 1
  • 1
Модуль ВК «Подписки » для Ucoz v 1.0
Дата: Четверг, 12.02.2026, 09:05 | Сообщение # 1 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:211
         Регистрация:20.10.2016

Скрипт предназначен для работы с модулем подписок на uCoz.

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



Принцип работы:

Подсчет количества тем
Скрипт не перебирает все темы и не анализирует каждый элемент страницы — это было бы медленно. Вместо этого он берет число тем из системного элемента, который уже показывает uCoz. Это позволяет почти мгновенно определить, есть ли изменения.

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

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

Загрузка темы и деталей
Для каждой темы скрипт получает:

Название темы

Ссылку на тему

Аватар (первое изображение в теме, если есть)

Описание темы (текст из span.thDescr, если присутствует)

Всё это собирается в блоки .subs-item, которые отображаются в контейнере.

Кликабельность

Клик по счетчику подписок открывает страницу списка подписок в той же вкладке.

Клик по блоку темы открывает тему в той же вкладке, при этом клики по ссылкам внутри блока игнорируются (чтобы не ломать стандартное поведение).

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

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

Код
<!-- ======= Блок подписок ======= -->
<div class="subs-block">
<div class="subs-header">Подписки</div>
<div class="subs-footer" id="subs-count">0 подписок</div>
<div class="subs-container" id="subs-container">
<div class="subs-empty">Загрузка подписок...</div>

</div>

</div>

<style>
/* ======= Общий блок ======= */
.subs-block {
background:#fff;
border-radius:6px;
box-shadow:0 2px 8px rgba(0,0,0,0.1);
overflow:hidden;
margin:2px 0;
font-family:Tahoma,sans-serif;
width:100%;
}

/* ======= Заголовок ======= */
.subs-header {
background:linear-gradient(135deg,#4a76a8,#5a88c0);
color:#fff;
padding:6px 8px;
font-weight:600;
font-size:13px; /* на 1px меньше */
}

/* ======= Контейнер подписок ======= */
.subs-container {
padding:1px;
display:flex;
flex-direction:column;
gap:0px;
max-height:480px;
overflow-y:auto;
}

/* ======= Элемент подписки ======= */
.subs-item {
display:flex;
align-items:flex-start;
gap:10px;
padding:8px;
cursor:pointer;
transition: background 0.2s;
}

.subs-item:hover {
background:#f0f2f5;
}

/* ======= Аватар ======= */
.subs-avatar {
width:60px;
height:60px;
border-radius:50%;
object-fit:cover;
flex-shrink:0;
border:2px solid #fff;
box-shadow:0 3px 8px rgba(0,0,0,0.12);
}

/* ======= Информация ======= */
.subs-info {
display:flex;
flex-direction:column;
overflow:visible;
}

.subs-title {
font-weight:700;
font-size:13px; /* на 1px меньше */
color:#2a5885;
margin-bottom:4px;
white-space:normal; /* перенос текста */
}

.subs-title a {
text-decoration:none;
color:inherit;
}

.subs-title a:hover {
text-decoration:underline;
}

.subs-desc {
font-size:12px; /* на 1px меньше */
color:#555;
line-height:1.4;
white-space:normal; /* полный текст */
}

/* ======= Пустой блок (узкий и ниже по высоте) ======= */
.subs-empty {
  padding:0px 0px; /* меньше сверху/снизу */
  text-align:center;
  color:#999;
  font-size:14px;
  max-width:300px; /* узкий блок по ширине */
  margin:20px auto; /* по центру контейнера */
  border-radius:6px; /* чуть скругления */
  background:#f9f9f9; /* легкий фон, чтобы выделялся */
  box-shadow:0 2px 6px rgba(0,0,0,0.05);
}

.subs-empty::before {
  content:"👥";
  display:block;
  font-size:30px; /* чуть меньше, чтобы не было огромного смайла */
  margin-bottom:8px;
  opacity:0.3;
}

/* ======= Подсчет с фоном как в старом ВК ======= */
.subs-footer {
background: linear-gradient(#fbf3c2, #ffe58a); /* фон старого ВК */
color: #6b5d00; /* цвет текста */
font-size:11px;
font-weight:700;
padding:4px 8px;
text-align:left;
border-top:1px solid #e6c700; /* легкий контур как ВК */
border-radius:0 0 0px 0px; /* скругляем низ блока */
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.8),
inset 0 -1px 0 rgba(0,0,0,0.06),
0 1px 1px rgba(0,0,0,0.08);
}

</style>

<script>
(async function() {
const container = document.getElementById('subs-container');
const counter = document.getElementById('subs-count');
const parser = new DOMParser();
const forumURL = '/forum/0-0-1-46';
const localKey = 'cachedSubs';

// Число подписок кликабельно
counter.style.cursor = 'pointer';
counter.title = 'Перейти к списку подписок';
counter.onclick = () => {
   // открываем в этой же вкладке
   window.location.href = forumURL;
};

// Функция, чтобы навесить клик на все блоки
function makeItemsClickable() {
  document.querySelectorAll('.subs-item').forEach(item => {
    item.style.cursor = 'pointer';
    item.onclick = e => {
      if(e.target.tagName.toLowerCase() === 'a') return;
      // открываем тему в этой же вкладке
      window.location.href = item.dataset.link;
    };
  });
}

// Сразу показываем кэш, если есть
let cached = JSON.parse(localStorage.getItem(localKey) || 'null');
if(cached){
   container.innerHTML = cached.html;
   counter.textContent = `${cached.count} подписок`;
   makeItemsClickable(); // навешиваем клики сразу
}

try {
   // Параллельно проверяем число тем на сервере
   let page = await fetch(forumURL, {credentials:'include'});
   let html = await page.text();
   let doc = parser.parseFromString(html,'text/html');

   let threadsTd = doc.querySelector('td.threadsDetails b');
   let count = threadsTd ? parseInt(threadsTd.textContent.trim()) : 0;
   counter.textContent = `${count} подписок`;

   // Если кэш есть и число не изменилось используем старый кэш и выходим
   if(cached && cached.count === count){
     console.log('Кэш актуален, обновление не требуется');
     return;
   }

   // Загружаем все темы заново
   let rows = doc.querySelectorAll('tr[id^="tt"]');
   if(!rows.length){
     container.innerHTML = '<div class="subs-empty">Нет подписок</div>';
     counter.textContent = '0 подписок';
     localStorage.removeItem(localKey);
     return;
   }

   let resultHTML = '';

   for(let row of rows){
     let topicEl = row.querySelector('.topic-item-title');
     if(!topicEl) continue;

     let topic = topicEl.textContent.trim();
     let topicLink = topicEl.href;

     let avatar = '/img/default_avatar.png';
     let topicDesc = '';

     try {
       let topicPage = await fetch(topicLink, {credentials:'include'});
       let topicHTML = await topicPage.text();
       let topicDoc = parser.parseFromString(topicHTML,'text/html');

       let firstImg = topicDoc.querySelector('.post-content-main img');
       if(firstImg) avatar = firstImg.src;

       let descEl = topicDoc.querySelector('span.thDescr');
       topicDesc = descEl ? descEl.textContent.trim() : '';
     } catch(e){
       console.warn('Ошибка загрузки темы', topicLink, e);
     }

     resultHTML += `
     <div class="subs-item" data-link="${topicLink}" style="cursor:pointer;">
       <img class="subs-avatar" src="${avatar}" alt="">
       <div class="subs-info">
         <div class="subs-title">${topic}</div>
         ${topicDesc ? `<div class="subs-desc">${topicDesc}</div>` : ''}
       </div>
     </div>
     `;
   }

   container.innerHTML = resultHTML;
   makeItemsClickable();

   // Сохраняем в кэш
   localStorage.setItem(localKey, JSON.stringify({count, html: resultHTML}));

} catch(e){
   if(!cached){
     container.innerHTML = '<div class="subs-empty">Ошибка загрузки подписок</div>';
     counter.textContent = '0 подписок';
   }
   console.error(e);
}
})();
</script>




Использовать подписки как «группы»
В uCoz можно подписываться на темы или разделы. По сути, это уже готовый механизм уведомлений и контроля доступа. Вместо того чтобы делать полноценный форумный раздел с кучей настроек, можно просто использовать подписку на тему как «группу участников».

Эмуляция страницы типа «ВКонтакте»
Можно сделать скрипт, который будет выводить не весь форум, а только те темы, на которые подписан пользователь.
Это будет выглядеть как лента новостей или «групповой стенки».

Подписка на тему - участие в группе.

Контент темы -посты группы.

Аватар, описание темы, количество сообщений - как карточка группы.

Контроль доступа

Если нужно, можно «запролить» тему или раздел: доступ только избранным пользователям.

Скрипт будет проверять подписки текущего пользователя и показывать только то, на что он подписан.

Таким образом, можно сделать закрытую мини-сеть внутри форума, где пользователи видят только свои «группы».

Удобство и кастомизация

Лента может быть полностью кастомной: только нужная информация, без лишних колонок форума.

Можно добавить аватар темы, последние посты, описание, количество новых сообщений.

Всё это формируется на клиентской стороне через скрипт, без необходимости переделывать серверную часть.

Почему это удобно

Не нужно создавать отдельную социальную сеть используешь существующую инфраструктуру форума.

Подписки уже хранятся в системе, это упрощает хранение и отображение контента.

Можно полностью контролировать визуальное оформление и логику показа.

Можно легко расширять функционал: лайки, комментарии, фильтры и т.д., просто добавляя JS на фронтенд.

Разработка "блока подписок" будет продолжатся..

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Четверг, 12.02.2026, 20:07 | Сообщение # 2 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:211
         Регистрация:20.10.2016

Это одна из самых ранних разработок. Основное внимание в ней было уделено дизайну визуальная часть находилась в приоритете. Сам же скрипт, его «сердце», довольно примитивен: по сути, это простой парсер, который обрабатывает лишь ограничённый набор данных и не содержит какой-либо логики. Иными словами, перед вами скрипт без «мозгов» простой, как одноклеточный организм.

Однако суть не в этом. Если вам понравился дизайн данного блока подписок, вы можете взять скриптовую часть из более продвинутого модуля и заменить её в результате вы получите знакомый дизайн, но уже с «новым мозгом» и расширенными возможностями современного модуля.

Код
<div class="vksub-block">
<div class="vksub-head">
<span>Подписки</span>
<span class="vksub-count" id="subsCount">0</span>
</div>
<div class="vksub-container">
<div id="subsParser" class="vksub-empty">Загрузка подписок...</div>
</div>
</div>

<script>
(async function(){

const box = document.getElementById("subsParser");
const countEl = document.getElementById("subsCount");
const parser = new DOMParser();

try {

   /* === грузим страницу подписок === */
   let page = await fetch('/forum/0-0-1-46', {credentials:'include'});
   let html = await page.text();
   let doc = parser.parseFromString(html,'text/html');

   let rows = doc.querySelectorAll('tr[id^="tt"]');

   if(!rows.length){
     box.className="vksub-empty";
     box.textContent="Нет подписок";
     countEl.textContent="0";
     return;
   }

   let resultHTML='';
   let delay=0;

   for(let row of rows){

     let topicEl = row.querySelector('.topic-item-title');
     if(!topicEl) continue;

     let topic = topicEl.textContent.trim();
     let topicLink = topicEl.href;

     /* === ИЗВЛЕКАЕМ ПЕРВУЮ КАРТИНКУ ИЗ ПЕРВОГО ПОСТА === */
     let avatar='/.s/img/icon/user.png';
     let topicDesc='';

     try{
       let topicPage = await fetch(topicLink,{credentials:'include'});
       let topicHTML = await topicPage.text();
       let topicDoc = parser.parseFromString(topicHTML,'text/html');

       // Первая картинка из поста
       let firstImg = topicDoc.querySelector('.post-content-main img');
       if(firstImg && firstImg.src) avatar = firstImg.src;

       // Описание темы
       let thDescrEl = topicDoc.querySelector('span.thDescr');
       topicDesc = thDescrEl ? thDescrEl.textContent.trim() : '';

     }catch(e){
       console.warn('Не удалось получить пост/картинку/описание', topicLink, e);
     }

     /* === HTML БЛОКА ПОДПИСКИ === */
     resultHTML += `
     <div class="vksub-item" style="animation-delay:${delay}ms">
       <img class="vksub-avatar" src="${avatar}" alt="">
       <div class="vksub-info">
         <div class="vksub-name">
           <a href="${topicLink}" target="_blank">${topic}</a>
         </div>
         ${topicDesc ? `<div class="vksub-desc" style="font-style:italic; color:#555; margin-top:3px;">${topicDesc}</div>` : ''}
       </div>
     </div>`;

     delay+=40;
   }

   box.className='';
   box.innerHTML=resultHTML;
   countEl.textContent=rows.length;

}catch(err){
   box.className="vksub-empty";
   box.textContent="Ошибка загрузки подписок";
   console.error(err);
}

})();
</script>

<style>
.vksub-block { background:#fff; border-radius:6px; border:1px solid #d8dce1; box-shadow:0 1px 4px rgba(0,0,0,0.08); margin-bottom:10px; overflow:hidden; font-family:Arial,sans-serif; }
.vksub-head { background:linear-gradient(#f7f7f7,#ececec); padding:10px 14px; border-bottom:1px solid #e1e5e8; font-size:14px; font-weight:bold; color:#2b587a; display:flex; justify-content:space-between; align-items:center; }
.vksub-head:hover { background:linear-gradient(#f9f9f9,#f0f0f0); }
.vksub-count { font-weight:normal; color:#68777d; font-size:12px; background:rgba(106,120,137,0.2); padding:2px 6px; border-radius:10px; transition:all .2s ease; }
.vksub-container { max-height:340px; overflow-y:auto; overflow-x:hidden; padding:4px 0; box-sizing:border-box; scrollbar-width:thin; scrollbar-color:#c8d4e1 #f7f7f7; }
.vksub-container::-webkit-scrollbar { width:6px; }
.vksub-container::-webkit-scrollbar-track { background:#f7f7f7; border-radius:3px; }
.vksub-container::-webkit-scrollbar-thumb { background:linear-gradient(#bdc7d8,#c8d4e1); border-radius:3px; border:1px solid #fff; }
.vksub-container::-webkit-scrollbar-thumb:hover { background:linear-gradient(#b0bdd0,#bdc7d8); }

.vksub-item { display:flex; align-items:center; padding:8px 12px; border-bottom:0px solid #f0f1f4; cursor:pointer; transition:all .2s ease; position:relative; min-height:52px; box-sizing:border-box; overflow:hidden; animation:vksubFadeSlideIn 0.25s ease forwards; }
.vksub-item:last-child { border-bottom:none; }
.vksub-item:hover { background:#edf3ff; }
.vksub-avatar { width:52px; height:52px; border-radius:50%; object-fit:cover; flex-shrink:0; margin-right:12px; border:2px solid #fff; box-shadow:0 2px 4px rgba(0,0,0,0.15); transition:transform .2s ease, box-shadow .2s ease; }
.vksub-item:hover .vksub-avatar { transform:scale(1.05); box-shadow:0 4px 12px rgba(0,0,0,0.25); }
.vksub-info { display:flex; flex-direction:column; flex:1 1 auto; min-width:0; overflow:hidden; box-sizing:border-box; padding-right:8px; }
.vksub-name { font-size:13px; font-weight:600; color:#2b587a; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; line-height:1.3; margin-bottom:2px; }
.vksub-name a { color:inherit; text-decoration:none; }
.vksub-name a:hover { text-decoration:underline; }
.vksub-desc { font-size:12px; color:#555; line-height:1.2; margin-top:2px; }
.vksub-status { font-size:12px; color:#68777d; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; line-height:1.2; }
.vksub-online { position:absolute; right:12px; top:14px; width:10px; height:10px; background:#5db15d; border-radius:50%; box-shadow:0 0 0 2px #fff; transition:all .2s ease; }
.vksub-item:hover .vksub-online { box-shadow:0 0 0 2px #edf3ff, 0 0 0 4px #5db15d; }
.vksub-empty { padding:40px 20px; text-align:center; color:#a9afba; font-size:13px; word-break:break-word; }
.vksub-empty::before { content:"👥"; display:block; font-size:48px; margin-bottom:12px; opacity:0.4; }
@keyframes vksubFadeSlideIn { from{opacity:0; transform:translateY(8px);} to{opacity:1; transform:translateY(0);} }
@media (max-width:900px) { .vksub-container{max-height:280px;} .vksub-item{padding:6px 10px; min-height:46px;} .vksub-avatar{width:36px; height:36px;} .vksub-name,.vksub-status{font-size:12px;} }
</style>


Мурчанн

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