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

1
Админ
Постов: 288
2
VIP
Постов: 75
3
Элита
Постов: 62
4
Проверенные
Постов: 46
5
Проверенные
Постов: 45
6
Проверенные
Постов: 39
7
VIP
Постов: 36
8
VIP
Постов: 35

  • Страница 1 из 1
  • 1
Скрипт центра уведомлений (Bell UI) V2.0 для uCoz
Дата: Понедельник, 01.06.2026, 04:08 | Сообщение # 1 | | Написал: Известный
Автор темы
Мурчанн не в сети
        Сообщений:288
         Регистрация:20.10.2016
За уважение окружающих За уважение окружающих За хорошее поведение За преданность форуму За любовь народа

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

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

Скрипт также был адаптирован под систему друзей для 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
         Регистрация:20.10.2016
За уважение окружающих За уважение окружающих За хорошее поведение За преданность форуму За любовь народа

Вторая версия

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



Код
<!-- Колокольчик -->
<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
         Регистрация:20.10.2016
За уважение окружающих За уважение окружающих За хорошее поведение За преданность форуму За любовь народа

Третья версия



Код
<!-- Колокольчик -->
<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
         Регистрация:20.10.2016
За уважение окружающих За уважение окружающих За хорошее поведение За преданность форуму За любовь народа

Сжатый исходный код
Экономим место.

Код
<!-- Колокольчик -->
<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>


Мурчанн

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