Untitled
unknown
plain_text
25 days ago
10 kB
40
No Index
// ==UserScript== // @name Custom Reddit notifications UI2 // @namespace https://reddit.com/ // @version Release 2 // @description Custom Reddit notifications UI with dark/light mode toggle and full functions // @author RaidPrincess // @match *://*.reddit.com/* // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; const baseUrl = 'https://www.reddit.com'; let open = false; let visibleItems = []; let lightMode = false; let toggleBtn; const container = document.createElement('div'); container.className = 'rnqv-container'; container.style.display = 'none'; document.body.appendChild(container); const style = document.createElement('style'); style.textContent = ` .rnqv-container { position: fixed; top: 55px; right: 10px; width: 360px; max-height: 420px; background: #1a1a1b; color: #d7dadc; border: 1px solid #343536; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.4); z-index: 9999; padding: 12px; overflow-y: auto; font-size: 14px; } .rnqv-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .rnqv-actions { display: flex; gap: 8px; } .rnqv-btn { background-color: transparent; border: 1px solid #343536; border-radius: 4px; padding: 4px 10px; color: inherit; cursor: pointer; font-size: 13px; } .rnqv-btn:hover { background-color: #272729; } .rnqv-container ul { list-style: none; margin: 0; padding: 0; } .rnqv-container li { margin-bottom: 10px; display: flex; align-items: center; gap: 8px; } .rnqv-container a { color: #ffffff; text-decoration: none; flex: 1; } .rnqv-container a:hover { text-decoration: underline; } .rnqv-avatar { width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0; } .rnqv-dismiss { cursor: pointer; font-size: 16px; color: #888; } .rnqv-dismiss:hover { color: red; } .rnqv-fake-ping { position: absolute; top: 6px; right: 8px; width: 12px; height: 12px; background: #ff2200; border-radius: 50%; z-index: 10; color: white; font-size: 9px; font-weight: bold; display: flex; align-items: center; justify-content: center; pointer-events: none; } [data-testid="unread-indicator"], #notifications-inbox-button span[class*="icon"] > span { opacity: 0 !important; pointer-events: none !important; } `; document.head.appendChild(style); function toggleLightMode() { lightMode = !lightMode; container.style.background = lightMode ? '#ffffff' : '#1a1a1b'; container.style.color = lightMode ? '#000000' : '#d7dadc'; container.querySelectorAll('a').forEach(a => a.style.color = lightMode ? '#000000' : '#ffffff'); if (toggleBtn) toggleBtn.textContent = lightMode ? '☾' : '☀︎'; } function replaceInboxButton() { const btn = document.querySelector('#notifications-inbox-button'); if (!btn || btn.dataset.rnqv === '1') return; const clone = btn.cloneNode(true); clone.id = 'notifications-inbox-button'; clone.style.position = 'relative'; clone.dataset.rnqv = '1'; clone.addEventListener('click', (e) => { e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); open = !open; container.style.display = open ? 'block' : 'none'; if (open) { loadNotifications(); setSeenCookie(); } }, true); btn.replaceWith(clone); refreshNotificationCount(); } function updateFakePing(count) { const btn = document.querySelector('#notifications-inbox-button'); if (!btn) return; let dot = btn.querySelector('.rnqv-fake-ping'); if (count > 0) { if (!dot) { dot = document.createElement('span'); dot.className = 'rnqv-fake-ping'; btn.appendChild(dot); } dot.textContent = count; } else if (dot) { dot.remove(); } } function setSeenCookie() { document.cookie = `rnqv_seen=true; path=/; expires=Fri, 31 Dec 9999 23:59:59 GMT`; } function getHiddenMessageIDs() { const match = document.cookie.match(/(?:^|; )rnqv_hidden=([^;]*)/); return match ? decodeURIComponent(match[1]).split(',') : []; } function setHiddenMessageIDs(ids) { const encoded = encodeURIComponent([...new Set(ids)].join(',')); document.cookie = `rnqv_hidden=${encoded}; path=/; expires=Fri, 31 Dec 9999 23:59:59 GMT`; } async function markMessageAsRead(id) { try { await fetch(`${baseUrl}/api/read_message`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'id=' + encodeURIComponent(id) }); } catch {} } async function refreshNotificationCount() { const hiddenIDs = getHiddenMessageIDs(); try { const res = await fetch(`${baseUrl}/message/unread.json`, { credentials: 'include' }); const items = (await res.json())?.data?.children || []; visibleItems = items.filter(i => !hiddenIDs.includes(i.data.name)); updateFakePing(visibleItems.length); } catch {} } async function loadNotifications() { container.innerHTML = ''; const header = document.createElement('div'); header.className = 'rnqv-header'; const title = document.createElement('strong'); title.textContent = 'Notifications'; header.appendChild(title); const fullBtn = document.createElement('button'); fullBtn.textContent = '🌐︎'; fullBtn.className = 'rnqv-btn'; fullBtn.onclick = () => window.open('https://www.reddit.com/notifications', '_blank'); toggleBtn = document.createElement('button'); toggleBtn.textContent = lightMode ? '☾' : '☀︎'; toggleBtn.className = 'rnqv-btn'; toggleBtn.onclick = toggleLightMode; const actions = document.createElement('div'); actions.className = 'rnqv-actions'; actions.appendChild(fullBtn); actions.appendChild(toggleBtn); header.appendChild(actions); container.appendChild(header); const list = document.createElement('ul'); container.appendChild(list); const hiddenIDs = getHiddenMessageIDs(); try { const res = await fetch(`${baseUrl}/message/unread.json`, { credentials: 'include' }); const items = (await res.json())?.data?.children?.slice(0, 10) || []; visibleItems = items.filter(i => !hiddenIDs.includes(i.data.name)); updateFakePing(visibleItems.length); if (!visibleItems.length) { const li = document.createElement('li'); li.textContent = 'No new notifications.'; list.appendChild(li); return; } for (const item of visibleItems) { const d = item.data; const li = document.createElement('li'); const avatar = document.createElement('img'); avatar.className = 'rnqv-avatar'; avatar.src = 'https://www.redditstatic.com/avatars/avatar_default_02_24A0ED.png'; li.appendChild(avatar); const a = document.createElement('a'); a.textContent = d.was_comment ? `u/${d.author}: ${(d.body || '').replace(/\s+/g, ' ').trim().slice(0, 80)}` : `${d.subject || '(no subject)'} (from u/${d.author})`; a.href = d.context ? baseUrl + d.context : baseUrl + '/message/messages/' + d.id; a.target = '_blank'; const dismiss = document.createElement('span'); dismiss.className = 'rnqv-dismiss'; dismiss.textContent = '🅧'; dismiss.title = 'Dismiss & mark as read'; function removeItem() { li.remove(); visibleItems = visibleItems.filter(v => v.data.name !== d.name); hiddenIDs.push(d.name); setHiddenMessageIDs(hiddenIDs); markMessageAsRead(d.name); updateFakePing(visibleItems.length); } a.addEventListener('click', removeItem); dismiss.addEventListener('click', removeItem); li.appendChild(a); li.appendChild(dismiss); list.appendChild(li); fetch(`${baseUrl}/user/${d.author}/about.json`, { credentials: 'include' }) .then(r => r.json()) .then(user => { const icon = user.data?.icon_img?.split('?')[0]; if (icon) avatar.src = icon; }).catch(() => {}); } } catch { const li = document.createElement('li'); li.textContent = 'Error loading notifications.'; list.appendChild(li); } } new MutationObserver(() => replaceInboxButton()).observe(document.body, { childList: true, subtree: true }); document.addEventListener('click', (e) => { if (open && !container.contains(e.target) && !e.target.closest('#notifications-inbox-button')) { container.style.display = 'none'; open = false; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && open) { container.style.display = 'none'; open = false; } }); container.addEventListener('click', (e) => e.stopPropagation()); function removeDynamicBadge() { const badge = document.querySelector('dynamic-badge[data-id="notification-count-element"]'); if (badge) badge.remove(); } removeDynamicBadge(); new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { removeDynamicBadge(); } } }).observe(document.body, { childList: true, subtree: true }); setInterval(removeDynamicBadge, 3000); setInterval(refreshNotificationCount, 10000); })();
Editor is loading...
Leave a Comment