Скрипт центра уведомлений (Bell UI) V2.0 для uCoz
Дата: Понедельник, 01.06.2026, 04:08 | Сообщение # 1 |
|
Написал: Известный
Автор темы
Мурчанн
не в сети
Сообщений: 288
Продолжаем улучшать систему уведомлений. Теперь они становятся системными, а не личными. Иными словами, скрипт отображает только уведомления системного характера. После прочтения такие сообщения больше не показываются в центре уведомлений, что делает систему чище и удобнее. Скрипт также был адаптирован под систему друзей для uCoz с функцией подтверждения заявок. Он отображает запросы на добавление в друзья, а также позволяет быстро подтвердить или отклонить их прямо из центра уведомлений. Кроме того, система уведомлений показывает важные изменения на сайте: 1. новые замечания 2. изменения репутации 3. и другие системные события Таким образом, скрипт объединяет ключевые уведомления в одном центре и делает работу с ними более удобной и структурированной. Скрипт изначально работает только с моим шаблоном, но логика его работы скрыта внутри, поэтому адаптировать его под другой дизайн не составляет особой сложности. Главная ценность здесь заключается не в самом оформлении, а в подходе в способе и логике.Исходный код .... Код
<!-- Колокольчик --> <div class="vk-alerts" id="vkAlerts"> <div class="vk-bell" id="vkBell"> <svg viewBox="0 0 24 24"> <path d="M12 22a2 2 0 0 0 2-2h-4a2 2 0 0 0 2 2zm6-6V11a6 6 0 1 0-12 0v5l-2 2v1h16v-1l-2-2z"/> </svg> <div class="vk-bell-count" id="vkBellCount">0</div> </div> <div class="vk-alerts-wrap" id="vkPopup"> <div class="vk-alert-tail"></div> <div class="vk-alerts-win" id="vkPopupContent"> <?if(!$USER_LOGGED_IN$)?> <div class="vk-alert-item"> <div class="vk-alert-title">Вы не авторизованы</div> <div class="vk-alert-text">Присоединяйтесь к сообществу 🙂</div> <a class="vk-alert-link" href="$LOGIN_LINK$">Авторизация</a> </div> <?endif?> <!-- Вакансия остаётся только здесь (один раз) --> <div class="vk-alert-item vk-alert-vacancy"> <div class="vk-alert-title">👉 Вакансии</div> <div class="vk-alert-text">Требуется контент-менеджер</div> <a class="vk-alert-link" href="#">Смотреть вакансии</a> </div> <div class="vk-alert-pm-wrap" id="vkAlertPMs"></div> </div> </div> </div> <style> #vkAlertPMs { display: flex; flex-direction: column; gap: 10px; max-height: 420px; overflow-y: auto; padding: 8px 4px; background: #f5f7fa; border-radius: 6px; } /* ========================= VK СИНИЕ УВЕДОМЛЕНИЯ ========================= */ .vk-alert-pm { display: flex; gap: 10px; align-items: center; background: linear-gradient(180deg, #ffffff 0%, #f7fbff 100%); border: 0px solid #d6e8ff; border-left: 0px solid #4a90e2; border-radius: 10px; padding: 11px 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); cursor: pointer; transition: all .2s ease; } /* hover как VK */ .vk-alert-pm:hover { background: #eef6ff; border-color: #bcd9ff; transform: translateY(-1px); box-shadow: 0 4px 10px rgba(74,144,226,0.15); } /* ========================= АНИМАЦИЯ НОВЫХ ========================= */ .vk-alert-pm.new { opacity: 0; transform: translateY(-8px); } .vk-alert-pm.new.show { opacity: 1; transform: translateY(0); transition: all .25s ease; } /* ========================= АВАТАР ========================= */ .vk-alert-pm img { width: 55px; height: 55px; border-radius: 50%; flex-shrink: 0; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,0.08); } /* ========================= ТЕКСТ ========================= */ .pm-title { font-weight: 600; font-size: 14px; color: #1f2d3d; margin-bottom: 3px; } .pm-desc { font-size: 13px; color: #5b6b7c; line-height: 1.35; } /* ========================= СТАТИЧЕСКИЕ БЛОКИ ========================= */ .vk-alert-item { background: #ffffff; border: 0px solid #e6eef8; border-radius: 8px; padding: 10px 12px; margin-bottom: 0px; } /* ========================= СКРОЛЛ (как у VK) ========================= */ #vkAlertPMs::-webkit-scrollbar { width: 6px; } #vkAlertPMs::-webkit-scrollbar-thumb { background: #c9ddf7; border-radius: 10px; } #vkAlertPMs::-webkit-scrollbar-track { background: transparent; } #vkAlertPMs { display: flex; flex-direction: column; gap: 4px; max-height: 420px; overflow-y: auto; padding: 8px 4px; background: #f5f7fa; border-radius: 6px; /* скрываем скролл */ scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE/Edge */ } /* Chrome / Safari / Opera */ #vkAlertPMs::-webkit-scrollbar { display: none; } </style> <script> const bell = document.getElementById("vkBell"); const popup = document.getElementById("vkPopup"); const pmWrap = document.getElementById("vkAlertPMs"); const bellCount = document.getElementById("vkBellCount"); const DEFAULT_AVA = "/files/person.png"; const CHECK_DELAY = 23000; /* ========================= СОСТОЯНИЕ ========================= */ let seen = new Set( JSON.parse(localStorage.getItem("vk_seen_notifications_v2") || "[]") ); const savedCount = localStorage.getItem("vk_pm_count_v1"); if (savedCount) bellCount.textContent = savedCount; function saveSeen() { localStorage.setItem( "vk_seen_notifications_v2", JSON.stringify([...seen]) ); } /* ========================= АНИМАЦИЯ КОЛОКОЛЬЧИКА ========================= */ function startBellIdleShake() { setInterval(() => { bell.classList.add("shake"); setTimeout(() => bell.classList.remove("shake"), 600); }, 6500); } function shakeBell() { bell.classList.add("shake"); setTimeout(() => bell.classList.remove("shake"), 600); } /* ========================= ОПИСАНИЯ УВЕДОМЛЕНИЙ ========================= */ function getDescription(title, sender) { const t = title.toLowerCase(); if (t.includes("завершение добавления в друзья")) { return `Пользователь «${sender}» принял вашу заявку в друзья. Теперь вы можете свободно общаться.`; } if (t.includes("дружб") || t.includes("предложение дружбы") || t.includes("заявка")) { return `Пользователь «${sender}» отправил вам заявку в друзья. Вы можете принять или отклонить её.`; } if (t.includes("сообщение") || t.includes("письмо")) { return `Пользователь «${sender}» отправил вам новое личное сообщение. Нажмите, чтобы прочитать.`; } if (t.includes("подарок") || t.includes("наград")) { return `Пользователь «${sender}» отправил вам подарок 🎁. Посмотрите его в профиле.`; } if (t.includes("замечан")) { return `Вам изменили уровень замечаний. Будьте внимательны к правилам сообщества.`; } return `Новое уведомление от пользователя «${sender}». Нажмите для просмотра.`; } /* ========================= UI ========================= */ bell.addEventListener("click", e => { e.stopPropagation(); popup.classList.toggle("active"); }); document.addEventListener("click", e => { if (!document.getElementById("vkAlerts").contains(e.target)) { popup.classList.remove("active"); } }); /* ========================= АВАТАР ========================= */ async function fetchAvatar(profileUrl) { if (!profileUrl) return DEFAULT_AVA; try { const html = await fetch(profileUrl + "?r=" + Date.now()) .then(r => r.text()); const div = document.createElement("div"); div.innerHTML = html; const bg = div.querySelector(".profile-photo")?.style.backgroundImage || ""; const match = bg.match(/url\(['"]?(.*?)['"]?\)/i); return match ? match[1] : DEFAULT_AVA; } catch { return DEFAULT_AVA; } } /* ========================= ПРОВЕРКА УВЕДОМЛЕНИЙ ========================= */ async function checkNotifications() { try { pmWrap.innerHTML = ""; const html = await fetch("/index/14?" + Date.now()) .then(r => r.text()); const div = document.createElement("div"); div.innerHTML = html; const notifications = div.querySelectorAll("b.unread"); /* СИСТЕМНЫЕ БЛОКИ (из DOM, НЕ из fetch) */ const systemBlocks = document.querySelectorAll( "#vkPopupContent > .vk-alert-item:not(.vk-alert-pm):not(.vk-alert-vacancy)" ); const vacancyCount = document.querySelectorAll( "#vkPopupContent > .vk-alert-vacancy" ).length; for (const b of notifications) { const row = b.closest("tr, li, div"); if (!row) continue; const title = b.textContent.trim(); const senderLink = row.querySelector("a[href*='/index/8-0-']"); const sender = senderLink ? senderLink.textContent.trim() : "Система"; const pmLink = row.querySelector("a[href*='/index/14']") || row.querySelector("a[href*='read']"); let href = pmLink ? pmLink.getAttribute("href") : "/index/14"; if (href.startsWith("/")) href = location.origin + href; const avatar = await fetchAvatar(senderLink?.href); const item = document.createElement("div"); item.className = "vk-alert-pm"; item.innerHTML = ` <img src="${avatar}"> <div style="flex:1;"> <div class="pm-title">${title}</div> <div class="pm-desc">${getDescription(title, sender)}</div> </div> `; item.onclick = () => location.href = href; pmWrap.appendChild(item); if (!seen.has(title + sender)) { seen.add(title + sender); saveSeen(); item.classList.add("new"); requestAnimationFrame(() => item.classList.add("show")); setTimeout(() => item.classList.remove("new"), 800); shakeBell(); } } /* ГЛАВНАЯ ПОЧИНКА */ const total = notifications.length + systemBlocks.length + vacancyCount; bellCount.textContent = total; localStorage.setItem("vk_pm_count_v1", total); } catch (err) { console.warn("Ошибка уведомлений:", err); } setTimeout(checkNotifications, CHECK_DELAY); } /* ========================= START ========================= */ checkNotifications(); startBellIdleShake(); </script>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Понедельник, 01.06.2026, 06:38 | Сообщение # 2 |
|
Написал: Известный
Автор темы
Мурчанн
не в сети
Сообщений: 288
Вторая версия Скрипт он как живой: с определённым интервалом заново перебирает системные уведомления и проверяет, есть ли новые.Код
<!-- Колокольчик --> <div class="vk-alerts" id="vkAlerts"> <div class="vk-bell" id="vkBell"> <svg viewBox="0 0 24 24"> <path d="M12 22a2 2 0 0 0 2-2h-4a2 2 0 0 0 2 2zm6-6V11a6 6 0 1 0-12 0v5l-2 2v1h16v-1l-2-2z"/> </svg> <div class="vk-bell-count" id="vkBellCount">0</div> </div> <div class="vk-alerts-wrap" id="vkPopup"> <div class="vk-alert-tail"></div> <div class="vk-alerts-win" id="vkPopupContent"> <?if(!$USER_LOGGED_IN$)?> <div class="vk-alert-item"> <div class="vk-alert-title">Вы не авторизованы</div> <div class="vk-alert-text">Присоединяйтесь к сообществу 🙂</div> <a class="vk-alert-link" href="$LOGIN_LINK$">Авторизация</a> </div> <?endif?> <!-- Вакансия остаётся только здесь (один раз) --> <div class="vk-alert-item vk-alert-vacancy"> <div class="vk-alert-title">👉 Вакансии</div> <div class="vk-alert-text">Требуется контент-менеджер</div> <a class="vk-alert-link" href="#">Смотреть вакансии</a> </div> <div class="vk-alert-pm-wrap" id="vkAlertPMs"></div> </div> </div> </div> <style> #vkAlertPMs { display: flex; flex-direction: column; gap: 10px; max-height: 520px; overflow-y: auto; padding: 8px 4px; background: #f5f7fa; border-radius: 6px; } /* ========================= VK СИНИЕ УВЕДОМЛЕНИЯ ========================= */ .vk-alert-pm { display: flex; gap: 10px; align-items: center; background: linear-gradient(180deg, #ffffff 0%, #f7fbff 100%); border: 0px solid #d6e8ff; border-left: 0px solid #4a90e2; border-radius: 20px 20px 4px 4px; padding: 11px 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); cursor: pointer; transition: all .2s ease; } /* hover как VK */ .vk-alert-pm:hover { background: #eef6ff; border-color: #bcd9ff; transform: translateY(-1px); box-shadow: 0 4px 10px rgba(74,144,226,0.15); } /* ========================= АНИМАЦИЯ НОВЫХ ========================= */ .vk-alert-pm.new { opacity: 0; transform: translateY(-8px); } .vk-alert-pm.new.show { opacity: 1; transform: translateY(0); transition: all .25s ease; } /* ========================= АВАТАР ========================= */ .vk-alert-pm img { width: 55px; height: 55px; border-radius: 5%; flex-shrink: 0; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,0.08); } /* ========================= ТЕКСТ ========================= */ .pm-title { font-weight: 600; font-size: 13px; color: #1f2d3d; margin-bottom: 3px; } .pm-desc { font-size: 11px; color: #5b6b7c; line-height: 1.35; } /* ========================= СТАТИЧЕСКИЕ БЛОКИ ========================= */ .vk-alert-item { background: #ffffff; border: 0px solid #e6eef8; border-radius: 8px; padding: 10px 12px; margin-bottom: 0px; } /* ========================= СКРОЛЛ (как у VK) ========================= */ #vkAlertPMs::-webkit-scrollbar { width: 6px; } #vkAlertPMs::-webkit-scrollbar-thumb { background: #c9ddf7; border-radius: 10px; } #vkAlertPMs::-webkit-scrollbar-track { background: transparent; } #vkAlertPMs { display: flex; flex-direction: column; gap: 4px; max-height: 420px; overflow-y: auto; padding: 8px 4px; background: #f5f7fa; border-radius: 6px; /* скрываем скролл */ scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE/Edge */ } /* Chrome / Safari / Opera */ #vkAlertPMs::-webkit-scrollbar { display: none; } </style> <script> const bell = document.getElementById("vkBell"); const popup = document.getElementById("vkPopup"); const pmWrap = document.getElementById("vkAlertPMs"); const bellCount = document.getElementById("vkBellCount"); const DEFAULT_AVA = "/files/person.png"; const CHECK_DELAY = 23000; /* ========================= СОСТОЯНИЕ ========================= */ let seen = new Set( JSON.parse(localStorage.getItem("vk_seen_notifications_v2") || "[]") ); const savedCount = localStorage.getItem("vk_pm_count_v1"); if (savedCount) bellCount.textContent = savedCount; function saveSeen() { localStorage.setItem( "vk_seen_notifications_v2", JSON.stringify([...seen]) ); } /* ========================= АНИМАЦИЯ КОЛОКОЛЬЧИКА ========================= */ function startBellIdleShake() { setInterval(() => { bell.classList.add("shake"); setTimeout(() => bell.classList.remove("shake"), 600); }, 6500); } function shakeBell() { bell.classList.add("shake"); setTimeout(() => bell.classList.remove("shake"), 600); } /* ========================= ОПИСАНИЯ УВЕДОМЛЕНИЙ ========================= */ function getDescription(title, sender) { const t = title.toLowerCase(); if (t.includes("завершение добавления в друзья")) { return `Пользователь «${sender}» принял вашу заявку в друзья. Теперь вы можете свободно общаться.`; } if (t.includes("дружб") || t.includes("предложение дружбы") || t.includes("заявка")) { return `Пользователь «${sender}» отправил вам заявку в друзья. Вы можете принять или отклонить её.`; } if (t.includes("сообщение") || t.includes("письмо")) { return `Пользователь «${sender}» отправил вам новое личное сообщение. Нажмите, чтобы прочитать.`; } if (t.includes("подарок") || t.includes("наград")) { return `Пользователь «${sender}» отправил вам подарок 🎁. Посмотрите его в профиле.`; } if (t.includes("замечан")) { return `Вам изменили уровень замечаний. Будьте внимательны к правилам сообщества.`; } return `Новое уведомление от пользователя «${sender}». Нажмите для просмотра.`; } /* ========================= UI ========================= */ bell.addEventListener("click", e => { e.stopPropagation(); popup.classList.toggle("active"); }); document.addEventListener("click", e => { if (!document.getElementById("vkAlerts").contains(e.target)) { popup.classList.remove("active"); } }); /* ========================= АВАТАР ========================= */ async function fetchAvatar(profileUrl) { if (!profileUrl) return DEFAULT_AVA; try { const html = await fetch(profileUrl + "?r=" + Date.now()) .then(r => r.text()); const div = document.createElement("div"); div.innerHTML = html; const bg = div.querySelector(".profile-photo")?.style.backgroundImage || ""; const match = bg.match(/url\(['"]?(.*?)['"]?\)/i); return match ? match[1] : DEFAULT_AVA; } catch { return DEFAULT_AVA; } } /* ========================= ПРОВЕРКА УВЕДОМЛЕНИЙ ========================= */ async function checkNotifications() { try { pmWrap.innerHTML = ""; const html = await fetch("/index/14?" + Date.now()) .then(r => r.text()); const div = document.createElement("div"); div.innerHTML = html; const notifications = div.querySelectorAll("b.unread"); /* СИСТЕМНЫЕ БЛОКИ (из DOM, НЕ из fetch) */ const systemBlocks = document.querySelectorAll( "#vkPopupContent > .vk-alert-item:not(.vk-alert-pm):not(.vk-alert-vacancy)" ); const vacancyCount = document.querySelectorAll( "#vkPopupContent > .vk-alert-vacancy" ).length; for (const b of notifications) { const row = b.closest("tr, li, div"); if (!row) continue; const title = b.textContent.trim(); const senderLink = row.querySelector("a[href*='/index/8-0-']"); const sender = senderLink ? senderLink.textContent.trim() : "Система"; const pmLink = row.querySelector("a[href*='/index/14']") || row.querySelector("a[href*='read']"); let href = pmLink ? pmLink.getAttribute("href") : "/index/14"; if (href.startsWith("/")) href = location.origin + href; const avatar = await fetchAvatar(senderLink?.href); const item = document.createElement("div"); item.className = "vk-alert-pm"; item.innerHTML = ` <img src="${avatar}"> <div style="flex:1;"> <div class="pm-title">${title}</div> <div class="pm-desc">${getDescription(title, sender)}</div> </div> `; item.onclick = () => location.href = href; pmWrap.appendChild(item); if (!seen.has(title + sender)) { seen.add(title + sender); saveSeen(); item.classList.add("new"); requestAnimationFrame(() => item.classList.add("show")); setTimeout(() => item.classList.remove("new"), 800); shakeBell(); } } /* ГЛАВНАЯ ПОЧИНКА */ const total = notifications.length + systemBlocks.length + vacancyCount; bellCount.textContent = total; localStorage.setItem("vk_pm_count_v1", total); } catch (err) { console.warn("Ошибка уведомлений:", err); } setTimeout(checkNotifications, CHECK_DELAY); } /* ========================= START ========================= */ checkNotifications(); startBellIdleShake(); </script>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Понедельник, 01.06.2026, 06:44 | Сообщение # 3 |
|
Написал: Известный
Автор темы
Мурчанн
не в сети
Сообщений: 288
Третья версия Код
<!-- Колокольчик --> <div class="vk-alerts" id="vkAlerts"> <div class="vk-bell" id="vkBell"> <svg viewBox="0 0 24 24"> <path d="M12 22a2 2 0 0 0 2-2h-4a2 2 0 0 0 2 2zm6-6V11a6 6 0 1 0-12 0v5l-2 2v1h16v-1l-2-2z"/> </svg> <div class="vk-bell-count" id="vkBellCount">0</div> </div> <div class="vk-alerts-wrap" id="vkPopup"> <div class="vk-alert-tail"></div> <div class="vk-alerts-win" id="vkPopupContent"> <?if(!$USER_LOGGED_IN$)?> <div class="vk-alert-item"> <div class="vk-alert-title">Вы не авторизованы</div> <div class="vk-alert-text">Присоединяйтесь к сообществу 🙂</div> <a class="vk-alert-link" href="$LOGIN_LINK$">Авторизация</a> </div> <?endif?> <!-- Вакансия остаётся только здесь (один раз) --> <div class="vk-alert-item vk-alert-vacancy"> <div class="vk-alert-title">👉 Вакансии</div> <div class="vk-alert-text">Требуется контент-менеджер</div> <a class="vk-alert-link" href="#">Смотреть вакансии</a> </div> <div class="vk-alert-pm-wrap" id="vkAlertPMs"></div> </div> </div> </div> <style> #vkAlertPMs { display: flex; flex-direction: column; gap: 10px; max-height: 520px; overflow-y: auto; padding: 8px 4px; background: #f5f7fa; border-radius: 6px; } /* ========================= VK СИНИЕ УВЕДОМЛЕНИЯ ========================= */ .vk-alert-pm { display: flex; gap: 10px; align-items: center; background: linear-gradient(180deg, #ffffff 0%, #f7fbff 100%); border: 0px solid #d6e8ff; border-left: 0px solid #4a90e2; border-radius: 20px 20px 4px 4px; padding: 11px 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); cursor: pointer; transition: all .2s ease; } /* hover как VK */ .vk-alert-pm:hover { background: #eef6ff; border-color: #bcd9ff; transform: translateY(-1px); box-shadow: 0 4px 10px rgba(74,144,226,0.15); } /* ========================= АНИМАЦИЯ НОВЫХ ========================= */ .vk-alert-pm.new { opacity: 0; transform: translateY(-8px); } .vk-alert-pm.new.show { opacity: 1; transform: translateY(0); transition: all .25s ease; } /* ========================= АВАТАР ========================= */ .vk-alert-pm img { width: 58px; height: 58px; border-radius: 100%; flex-shrink: 0; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,0.08); } /* ========================= ТЕКСТ ========================= */ .pm-title { font-weight: 600; font-size: 13px; color: #1f2d3d; margin-bottom: 3px; } .pm-desc { font-size: 11px; color: #5b6b7c; line-height: 1.35; } /* ========================= СТАТИЧЕСКИЕ БЛОКИ ========================= */ .vk-alert-item { background: #ffffff; border: 0px solid #e6eef8; border-radius: 8px; padding: 10px 12px; margin-bottom: 0px; } /* ========================= СКРОЛЛ (как у VK) ========================= */ #vkAlertPMs::-webkit-scrollbar { width: 6px; } #vkAlertPMs::-webkit-scrollbar-thumb { background: #c9ddf7; border-radius: 10px; } #vkAlertPMs::-webkit-scrollbar-track { background: transparent; } #vkAlertPMs { display: flex; flex-direction: column; gap: 4px; max-height: 420px; overflow-y: auto; padding: 8px 4px; background: #f5f7fa; border-radius: 6px; /* скрываем скролл */ scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE/Edge */ } /* Chrome / Safari / Opera */ #vkAlertPMs::-webkit-scrollbar { display: none; } </style> <script> const bell = document.getElementById("vkBell"); const popup = document.getElementById("vkPopup"); const pmWrap = document.getElementById("vkAlertPMs"); const bellCount = document.getElementById("vkBellCount"); const DEFAULT_AVA = "/files/person.png"; const CHECK_DELAY = 23000; /* ========================= СОСТОЯНИЕ ========================= */ let seen = new Set( JSON.parse(localStorage.getItem("vk_seen_notifications_v2") || "[]") ); const savedCount = localStorage.getItem("vk_pm_count_v1"); if (savedCount) bellCount.textContent = savedCount; function saveSeen() { localStorage.setItem( "vk_seen_notifications_v2", JSON.stringify([...seen]) ); } /* ========================= АНИМАЦИЯ КОЛОКОЛЬЧИКА ========================= */ function startBellIdleShake() { setInterval(() => { bell.classList.add("shake"); setTimeout(() => bell.classList.remove("shake"), 600); }, 6500); } function shakeBell() { bell.classList.add("shake"); setTimeout(() => bell.classList.remove("shake"), 600); } /* ========================= ОПИСАНИЯ УВЕДОМЛЕНИЙ ========================= */ function getDescription(title, sender) { const t = title.toLowerCase(); if (t.includes("завершение добавления в друзья")) { return `Пользователь «${sender}» принял вашу заявку в друзья. Теперь вы можете свободно общаться.`; } if (t.includes("дружб") || t.includes("предложение дружбы") || t.includes("заявка")) { return `Пользователь «${sender}» отправил вам заявку в друзья. Вы можете принять или отклонить её.`; } if (t.includes("сообщение") || t.includes("письмо")) { return `Пользователь «${sender}» отправил вам новое личное сообщение. Нажмите, чтобы прочитать.`; } if (t.includes("подарок") || t.includes("наград")) { return `Пользователь «${sender}» отправил вам подарок 🎁. Посмотрите его в профиле.`; } if (t.includes("замечан")) { return `Вам изменили уровень замечаний. Будьте внимательны к правилам сообщества.`; } return `Новое уведомление от пользователя «${sender}». Нажмите для просмотра.`; } /* ========================= UI ========================= */ bell.addEventListener("click", e => { e.stopPropagation(); popup.classList.toggle("active"); }); document.addEventListener("click", e => { if (!document.getElementById("vkAlerts").contains(e.target)) { popup.classList.remove("active"); } }); /* ========================= АВАТАР ========================= */ async function fetchAvatar(profileUrl) { if (!profileUrl) return DEFAULT_AVA; try { const html = await fetch(profileUrl + "?r=" + Date.now()) .then(r => r.text()); const div = document.createElement("div"); div.innerHTML = html; const bg = div.querySelector(".profile-photo")?.style.backgroundImage || ""; const match = bg.match(/url\(['"]?(.*?)['"]?\)/i); return match ? match[1] : DEFAULT_AVA; } catch { return DEFAULT_AVA; } } /* ========================= ПРОВЕРКА УВЕДОМЛЕНИЙ ========================= */ async function checkNotifications() { try { pmWrap.innerHTML = ""; const html = await fetch("/index/14?" + Date.now()) .then(r => r.text()); const div = document.createElement("div"); div.innerHTML = html; const notifications = div.querySelectorAll("b.unread"); /* СИСТЕМНЫЕ БЛОКИ (из DOM, НЕ из fetch) */ const systemBlocks = document.querySelectorAll( "#vkPopupContent > .vk-alert-item:not(.vk-alert-pm):not(.vk-alert-vacancy)" ); const vacancyCount = document.querySelectorAll( "#vkPopupContent > .vk-alert-vacancy" ).length; for (const b of notifications) { const row = b.closest("tr, li, div"); if (!row) continue; const title = b.textContent.trim(); const senderLink = row.querySelector("a[href*='/index/8-0-']"); const sender = senderLink ? senderLink.textContent.trim() : "Система"; const pmLink = row.querySelector("a[href*='/index/14']") || row.querySelector("a[href*='read']"); let href = pmLink ? pmLink.getAttribute("href") : "/index/14"; if (href.startsWith("/")) href = location.origin + href; const avatar = await fetchAvatar(senderLink?.href); const item = document.createElement("div"); item.className = "vk-alert-pm"; item.innerHTML = ` <img src="${avatar}"> <div style="flex:1;"> <div class="pm-title">${title}</div> <div class="pm-desc">${getDescription(title, sender)}</div> </div> `; item.onclick = () => location.href = href; pmWrap.appendChild(item); if (!seen.has(title + sender)) { seen.add(title + sender); saveSeen(); item.classList.add("new"); requestAnimationFrame(() => item.classList.add("show")); setTimeout(() => item.classList.remove("new"), 800); shakeBell(); } } /* ГЛАВНАЯ ПОЧИНКА */ const total = notifications.length + systemBlocks.length + vacancyCount; bellCount.textContent = total; localStorage.setItem("vk_pm_count_v1", total); } catch (err) { console.warn("Ошибка уведомлений:", err); } setTimeout(checkNotifications, CHECK_DELAY); } /* ========================= START ========================= */ checkNotifications(); startBellIdleShake(); </script>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Среда, 03.06.2026, 03:17 | Сообщение # 4 |
|
Написал: Известный
Автор темы
Мурчанн
не в сети
Сообщений: 288
Сжатый исходный код Экономим место.Код
<!-- Колокольчик --> <div class="vk-alerts" id="vkAlerts"> <div class="vk-bell" id="vkBell"> <svg viewBox="0 0 24 24"> <path d="M12 22a2 2 0 0 0 2-2h-4a2 2 0 0 0 2 2zm6-6V11a6 6 0 1 0-12 0v5l-2 2v1h16v-1l-2-2z"/> </svg> <div class="vk-bell-count" id="vkBellCount">0</div> </div> <div class="vk-alerts-wrap" id="vkPopup"> <div class="vk-alert-tail"></div> <div class="vk-alerts-win" id="vkPopupContent"> <?if(!$USER_LOGGED_IN$)?> <div class="vk-alert-item"> <div class="vk-alert-title">Вы не авторизованы</div> <div class="vk-alert-text">Присоединяйтесь к сообществу 🙂</div> <a class="vk-alert-link" href="$LOGIN_LINK$">Авторизация</a> </div> <?endif?> <!-- Вакансия остаётся только здесь (один раз) --> <div class="vk-alert-item vk-alert-vacancy"> <div class="vk-alert-title">👉 Вакансии</div> <div class="vk-alert-text">Требуется контент-менеджер</div> <a class="vk-alert-link" href="#">Смотреть вакансии</a> </div> <div class="vk-alert-pm-wrap" id="vkAlertPMs"></div> </div> </div> </div> <link rel="stylesheet" href="https://bro.usite.pro/css/vk-glocke.css" media="all"> <script> window.VK_NOTIFY_CONFIG = { loggedIn: <?if($USER_LOGGED_IN$)?>true<?else?>false<?endif?>, loginLink: '$LOGIN_LINK$' }; </script> <script src="https://bro.usite.pro/js/vk-glocke.js"></script>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.