// ==UserScript==
// @name Кастомизация lolz.live
// @namespace https://lolz.live/
// @version 0.2
// @description дескриптион
// @match https://lolz.live/*
// @match https://lzt.market/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
'use strict';
if (window.__LL_SCRIPT_INIT__) return;
window.__LL_SCRIPT_INIT__ = true;
const LS = {
toggles: 'll_custom_enable_block_toggles',
bgEnable: 'll_custom_enable_bg',
bgData: 'll_custom_bg_data',
collapse: 'll_custom_enable_alerts_collapse',
menu: 'll_custom_enable_menu_editor',
reply: 'll_custom_enable_alert_inline_reply',
timeFull: 'll_custom_enable_full_time',
copy: 'll_custom_enable_copy_tools',
soundAlerts: 'll_custom_enable_sound_alerts',
soundAlertsData: 'll_custom_sound_alerts_data',
soundMsgs: 'll_custom_enable_sound_msgs',
soundMsgsData: 'll_custom_sound_msgs_data',
alertsSort: 'll_enable_alerts_sort',
hideHot: 'll_custom_hide_hot_threads',
cat: id => `ll_cat_collapse_${id}`,
};
const on = k => localStorage.getItem(k) === '1';
const set = (k, v) => localStorage.setItem(k, v ? '1' : '0');
const isPrefs = () => location.pathname.startsWith('/account/preferences');
const safe = fn => { try { fn(); } catch (e) { console.error('[ll]', e); } };
const wantNames = new Set([
'Основная категория','Тематическая категория','Игровая категория','Общая категория','Пользовательские разделы',
]);
(function () {
const s = document.createElement('style');
s.textContent = `
html.ll-bg-on{background:var(--ll-bg-url) center/cover no-repeat fixed!important;background-color:#000!important}
html.ll-bg-on body{background:transparent!important}
.tm-cat-toggle{cursor:pointer;display:inline-block;width:16px;height:16px;margin-right:6px;vertical-align:middle;transition:transform .2s}
.tm-cat-toggle::before{content:"▾";font-size:16px;line-height:16px;display:inline-block}
.tm-cat-collapsed > .categoryNodeInfo .tm-cat-toggle{transform:rotate(-90deg)}
.ll-reply-bar{display:flex;gap:10px;align-items:center;margin-left:auto}
.ll-reply-btn{cursor:pointer;font-size:12px;padding:2px 6px;border:1px solid #444;border-radius:4px;background:#2a2a2a;color:#ddd}
.ll-reply-wrap{margin-top:8px;background:#222;border:1px solid #3a3a3a;border-radius:6px;padding:8px}
.ll-reply-wrap textarea{width:100%;min-height:52px;resize:vertical;box-sizing:border-box;background:#111;color:#ddd;border:1px solid #444;border-radius:4px;padding:6px 8px}
.ll-reply-actions{margin-top:6px;display:flex;gap:8px;align-items:center}
.ll-reply-send,.ll-reply-cancel{padding:4px 10px;border-radius:4px;border:1px solid #3a3a3a;background:#2a2a2a;color:#ddd;cursor:pointer}
.ll-reply-send[disabled]{opacity:.6;cursor:default}
.ll-reply-msg{font-size:12px;color:#8ad}
.ll-sound-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.ll-sound-row .ll-inline-btn{padding:4px 8px;font-size:12px;border-radius:6px;border:1px solid #444;background:#303030;color:#ddd;cursor:pointer;margin-left:6px}
.ll-sound-row .ll-inline-btn:hover{background:#383838}
.ll-hide-hot .hotThreadsContainer{display:none!important}
.manageItems.editMode .manageItem{position:relative}
.itemCloser{position:absolute;top:2px;right:6px;font-size:18px;color:#e00;cursor:pointer;line-height:1}
#addCustomItemBtn.manageItem{display:none;align-items:center;padding:8px 12px;gap:12px;height:52px;box-sizing:border-box;border-radius:8px;background-color:#2d2d2d;color:#aaa;text-decoration:none;transition:background-color .2s,color .2s}
#addCustomItemBtn.manageItem:hover{background-color:#303030}
#addCustomItemBtn.manageItem:hover span{color:#37D38D}
#addCustomItemBtn.manageItem .SvgIcon svg{fill:#888;transition:fill .2s}
#addCustomItemBtn.manageItem:hover .SvgIcon svg{fill:#37D38D}
#customItemOverlay{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#2d2d2d;color:#ccc;padding:20px;border-radius:8px;z-index:9999;min-width:460px;box-shadow:0 0 15px rgba(0,0,0,.6)}
#customItemOverlay fieldset{border:none;margin:0;padding:0}
#customItemOverlay .ctrlUnit{display:flex;align-items:center;margin-bottom:12px}
#customItemOverlay .ctrlUnit dt{width:120px;margin:0;font-weight:500;color:#aaa}
#customItemOverlay .ctrlUnit dd{flex:1;margin:0}
#customItemOverlay .textCtrl{width:100%;padding:6px 8px;border:1px solid #444;border-radius:4px;background:#1f1f1f;color:#ddd}
#customItemOverlay .textCtrl:focus{border-color:#37D38D;outline:none}
#customItemOverlay .sectionFooter{margin-top:15px;text-align:right}
#customItemOverlay .button{margin-left:8px}
#ll-prefs-customization ul > li.ll-sound-li{ margin:0; }
#ll-prefs-customization ul > li.ll-sound-li .ll-inline-btn{ margin-left:6px; }
`;
document.head.appendChild(s);
})();
async function copyText(text){
if(!text) return false;
try{
await navigator.clipboard.writeText(text);
return true;
}catch{
const ta=document.createElement('textarea');
ta.value=text; ta.style.cssText='position:fixed;left:-9999px;top:0';
document.body.appendChild(ta);
ta.focus(); ta.select();
let ok=false; try{ ok=document.execCommand('copy'); } finally { ta.remove(); }
return ok;
}
}
async function getFirstPostIdFromThread(threadId) {
const res = await fetch(`/threads/${threadId}/`, { credentials: 'include' });
const html = await res.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const li = doc.querySelector('li[id^="post-"]');
if (!li) throw new Error('postId не найден (thread)');
return (li.id.match(/post-(\d+)/) || [])[1];
}
async function getPostIdFromAlert(li) {
const hrefA = li.querySelector('a[href^="posts/"]');
if (hrefA) {
const m = hrefA.getAttribute('href').match(/posts\/(\d+)\//);
if (m) return m[1];
}
const previewA = li.querySelector('.PreviewTooltip[data-previewurl]');
if (previewA) {
const pv = String(previewA.getAttribute('data-previewurl') || '');
let m = pv.match(/posts\/(\d+)\/preview/);
if (m) return m[1];
m = pv.match(/threads\/(\d+)\/preview/);
if (m) return await getFirstPostIdFromThread(m[1]);
}
const liPost = li.closest('li[id^="post-"]');
if (liPost) {
const m = liPost.id.match(/post-(\d+)/);
if (m) return m[1];
}
throw new Error('postId не найден');
}
function getCsrfLikeFields(formOpt) {
const root = formOpt || document;
const q = s => root.querySelector(s) || document.querySelector(s);
return {
_xfToken: q('input[name="_xfToken"]')?.value || '',
last_date: q('input[name="last_date"]')?.value || '',
last_known_date: q('input[name="last_known_date"]')?.value || '',
};
}
(function observeAlerts() {
const targets = ['#AlertsDestinationWrapper', '#AccountMenu'];
const mo = new MutationObserver(() => { try { injectReplyButtonsForAlerts(); } catch {} });
targets.forEach(sel => {
const n = document.querySelector(sel);
if (n) mo.observe(n, { childList: true, subtree: true });
});
})();
function getPostPlainText(node) {
const box = node.querySelector('blockquote.messageText');
if (box) {
return box.textContent.replace(/\u00A0/g, ' ').trim();
}
return '';
}
function applyBg(dataUrl){
if(!dataUrl) return;
document.documentElement.style.setProperty('--ll-bg-url', `url("${dataUrl}")`);
document.documentElement.classList.add('ll-bg-on');
}
function clearBg(){
document.documentElement.classList.remove('ll-bg-on');
document.documentElement.style.removeProperty('--ll-bg-url');
}
function pickBg(){
const i=document.createElement('input'); i.type='file'; i.accept='image/*';
i.onchange=()=>{ const f=i.files&&i.files[0]; if(!f) return;
const r=new FileReader();
r.onload=()=>{ const d=r.result; localStorage.setItem(LS.bgData,d); applyBg(d); set(LS.bgEnable,true); };
r.readAsDataURL(f);
};
i.click();
}
function injectPrefs(){
const form=document.querySelector('form[action*="preferences-save"]'); if(!form) return false;
const submitDl=form.querySelector('dl.ctrlUnit.submitUnit'); if(!submitDl) return false;
if (form.querySelector('#ll-prefs-customization')) return true;
const dl=document.createElement('dl'); dl.className='ctrlUnit'; dl.id='ll-prefs-customization';
const dt=document.createElement('dt'); const dd=document.createElement('dd');
const h3=document.createElement('h3'); h3.textContent='Кастомизация'; h3.style.fontWeight='700';
const ul=document.createElement('ul');
const mkLi=(id,text,key)=>{
const li=document.createElement('li');
const input=document.createElement('input'); input.type='checkbox'; input.id=id; input.name=id; input.value='1'; input.checked=on(key);
const label=document.createElement('label'); label.htmlFor=id; label.textContent=text;
li.appendChild(input); li.appendChild(label);
if(key===LS.bgEnable){
input.addEventListener('change',()=>{
if(input.checked){
set(LS.bgEnable,true);
const s=localStorage.getItem(LS.bgData); if(s) applyBg(s); else pickBg();
} else {
set(LS.bgEnable,false); clearBg();
}
});
const reset=document.createElement('button'); reset.type='button'; reset.className='ll-inline-btn'; reset.textContent='Сбросить фон';
reset.onclick=()=>{ localStorage.removeItem(LS.bgData); clearBg();};
const change=document.createElement('button'); change.type='button'; change.className='ll-inline-btn'; change.textContent='Выбрать файл';
change.onclick=()=>pickBg();
li.appendChild(reset); li.appendChild(change);
} else {
input.addEventListener('change',()=>set(key,input.checked));
}
return li;
};
function pickAudio(dataKey, enableKey){
const i=document.createElement('input'); i.type='file'; i.accept='audio/*';
i.onchange=()=>{ const f=i.files&&i.files[0]; if(!f) return;
if (f.size > 3*1024*1024) { alert('Файл > 3 МБ'); return; }
const r=new FileReader();
r.onload=()=>{ localStorage.setItem(dataKey, r.result); set(enableKey, true); enableSoundSystem(); };
r.readAsDataURL(f);
};
i.click();
}
function mkSoundLi(id, label, enableKey, dataKey){
const li=document.createElement('li'); li.className='ll-sound-li';
const input=document.createElement('input'); input.type='checkbox'; input.id=id; input.checked=on(enableKey);
const lab=document.createElement('label'); lab.htmlFor=id; lab.textContent=label;
const pick=document.createElement('button'); pick.type='button'; pick.className='ll-inline-btn'; pick.textContent='Выбрать звук';
pick.onclick=()=>pickAudio(dataKey, enableKey);
const del=document.createElement('button'); del.type='button'; del.className='ll-inline-btn'; del.textContent='Удалить звук';
del.onclick=()=>{ localStorage.removeItem(dataKey); set(enableKey,false); disableSoundSystemIfNone(); };
li.append(input, lab, pick, del);
input.addEventListener('change',()=>{
set(enableKey, input.checked);
if (input.checked) enableSoundSystem(); else disableSoundSystemIfNone();
});
return li;
}
ul.appendChild(mkSoundLi('ctrl_ll_enable_sound_alerts','Включить звук для уведомлений',LS.soundAlerts,LS.soundAlertsData));
ul.appendChild(mkSoundLi('ctrl_ll_enable_sound_msgs','Включить звук для сообщений',LS.soundMsgs,LS.soundMsgsData));
ul.appendChild(mkLi('ctrl_ll_enable_block_toggles','Включить сворачивание блоков с разделами',LS.toggles));
ul.appendChild(mkLi('ctrl_ll_enable_bg','Добавить кастомный фон',LS.bgEnable));
ul.appendChild(mkLi('ctrl_ll_enable_alerts_collapse','Схлопывать дубли уведомлений',LS.collapse));
ul.appendChild(mkLi('ctrl_ll_enable_menu_editor','Включить редактор пунктов меню',LS.menu));
ul.appendChild(mkLi('ctrl_ll_enable_alert_inline_reply','Включить ответ на сообщение через меню уведомлений',LS.reply));
ul.appendChild(mkLi('ctrl_ll_enable_full_time','Показывать полное время сообщений',LS.timeFull));
ul.appendChild(mkLi('ctrl_ll_enable_copy','Включить кнопки копирования в меню сообщений',LS.copy));
ul.appendChild(mkLi('ctrl_ll_enable_alerts_sort','Сортировка уведомлений',LS.alertsSort));
ul.appendChild(mkLi('ctrl_ll_hide_hot','Скрывать горячие темы',LS.hideHot));
dd.addEventListener('change', (e) => {
if (e.target && e.target.id === 'ctrl_ll_enable_copy') {
if (e.target.checked) { set(LS.copy,true); enableCopyLinkInMenu(); }
else { set(LS.copy,false); disableCopyLinkInMenu(); }
}
if (e.target && e.target.id === 'ctrl_ll_enable_alert_inline_reply') {
if (e.target.checked) { set(LS.reply, true); enableAlertInlineReply(); }
else { set(LS.reply, false); disableAlertInlineReply(); }
}
if (e.target && e.target.id === 'ctrl_ll_enable_alerts_sort') {
if (e.target.checked) { set(LS.alertsSort,true); enableAlertsSorting(); }
else { set(LS.alertsSort,false); disableAlertsSorting(); }
}
if (e.target && e.target.id === 'ctrl_ll_hide_hot') {
set(LS.hideHot, e.target.checked);
enableHideHotThreads();
}
});
dd.appendChild(h3); dd.appendChild(ul); dl.appendChild(dt); dl.appendChild(dd); form.insertBefore(dl, submitDl);
return true;
}
function enableHideHotThreads(){
document.documentElement.classList.toggle('ll-hide-hot', on(LS.hideHot));
}
function disableAlertInlineReply(){
window.__LL_ALERTS_DISABLED__ = true;
document.querySelectorAll('.ll-reply-wrap').forEach(n=>n.remove());
document.querySelectorAll('.ll-reply-bar').forEach(n=>n.remove());
document.querySelectorAll('li.Alert[data-ll-reply-btn]')
.forEach(li=>li.removeAttribute('data-ll-reply-btn'));
window.__LL_ALERTS_WIRED__ = false;
}
function disableCopyLinkInMenu(){
window.__LL_COPY_MENU_DISABLED__ = true;
const mo = window.__LL_COPY_MENU_OBS__;
if (mo && mo.disconnect) mo.disconnect();
document.querySelectorAll('.ll-copy-link,.ll-copy-text').forEach(el=>el.remove());
document.querySelectorAll('.blockLinksList.have-icon[data-ll-copy-wired]').forEach(el=>el.removeAttribute('data-ll-copy-wired'));
window.__LL_COPY_MENU_WIRED__ = false;
}
function addCategoryToggles(){
if(!on(LS.toggles)) return;
document.querySelectorAll('li.node.category.level_1').forEach(cat=>{
if (cat.dataset.llToggleInit === '1') return;
const title=cat.querySelector('.categoryNodeInfo .categoryText h3.nodeTitle'); if(!title) return;
const name=title.textContent.trim(); if(!wantNames.has(name)) { cat.dataset.llToggleInit='1'; return; }
const list=cat.querySelector('ol.nodeList'); if(!list) { cat.dataset.llToggleInit='1'; return; }
if (!title.querySelector('.tm-cat-toggle')) {
const btn=document.createElement('span'); btn.className='tm-cat-toggle'; title.prepend(btn);
const id=cat.getAttribute('id')||name;
const collapsed=localStorage.getItem(LS.cat(id))==='1';
if(collapsed){ list.style.display='none'; cat.classList.add('tm-cat-collapsed'); }
btn.addEventListener('click',e=>{
e.preventDefault();
const hidden=list.style.display==='none';
list.style.display=hidden?'':'none';
cat.classList.toggle('tm-cat-collapsed',!hidden);
localStorage.setItem(LS.cat(id),hidden?'0':'1');
});
}
cat.dataset.llToggleInit='1';
});
}
function enableAlertsCollapse(){
if(!on(LS.collapse)) return;
if (window.__LL_COLLAPSE_WIRED__) return;
window.__LL_COLLAPSE_WIRED__ = true;
const debounce=(fn,d=200)=>{ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a),d); }; };
const run=debounce(()=>{
const list=document.querySelector('#AlertsDestinationWrapper ol'); if(!list) return;
const buckets=new Map();
[...list.querySelectorAll('li.Alert')].forEach(li=>{
const isLike=li.querySelector('.alertAction.like2') || /нравится/i.test(li.textContent);
const type=isLike?'like':'reply';
let key; if(isLike) key=li.querySelector('h3 a[href*="posts/"]')?.href?.match(/posts\/(\d+)/)?.[1];
else key=li.querySelector('h3 a[data-previewurl]')?.dataset.previewurl?.match(/threads\/(\d+)/)?.[1];
if(!key) return; const k=`${type}_${key}`; (buckets.get(k)||buckets.set(k,[]).get(k)).push(li);
});
buckets.forEach(group=>{
if(group.length<2) return;
const [first,...rest]=group; const others=rest.length; rest.forEach(li=>li.remove());
const h3=first.querySelector('h3'); const nick=h3?.querySelector('a.username'); if(!h3||!nick) return;
if(h3.querySelector('.tm-counter')) return;
const c=document.createElement('span'); c.className='tm-counter'; c.textContent=` (и ещё ${others})`; c.style.color='#fff'; c.style.marginLeft='4px'; nick.after(c);
});
});
run();
const panel=document.getElementById('AlertPanels');
if(panel) new MutationObserver(run).observe(panel,{childList:true,subtree:true});
}
function composeFull(el){
const ds = el.getAttribute('data-datestring') || '';
const ts = el.getAttribute('data-timestring') || '';
const t = el.getAttribute('title') || '';
if (ds && ts) return `${ds} в ${ts}`;
if (t) return t;
const sec = Number(el.getAttribute('data-time'));
if (!Number.isNaN(sec)) {
const d = new Date(sec*1000);
const dd = d.toLocaleDateString('ru-RU', { day:'2-digit', month:'short', year:'numeric' }).replace('.', '');
const tt = d.toLocaleTimeString('ru-RU', { hour:'2-digit', minute:'2-digit' });
return `${dd} в ${tt}`;
}
return el.textContent || '';
}
function applyFullTimeOnNode(root=document){
root.querySelectorAll('.DateTime:not([data-ll-full])').forEach(el=>{
el.dataset.llOrigTxt = el.textContent || '';
el.textContent = composeFull(el);
el.dataset.llFull = '1';
});
}
function revertFullTime(root=document){
root.querySelectorAll('.DateTime[data-ll-full]').forEach(el=>{
if (el.dataset.llOrigTxt) el.textContent = el.dataset.llOrigTxt;
el.removeAttribute('data-ll-full');
el.removeAttribute('data-ll-orig-txt');
});
}
function enableFullTimeDates(){
if (on(LS.timeFull)) applyFullTimeOnNode(document);
if (window.__LL_FULLTIME_OBS__) return;
const mo = new MutationObserver(muts=>{
if (!on(LS.timeFull)) return;
for (const m of muts){
for (const n of m.addedNodes){
if (n.nodeType === 1) applyFullTimeOnNode(n);
}
}
});
const body = document.body || document.documentElement;
if (body) { mo.observe(body, {childList:true, subtree:true}); window.__LL_FULLTIME_OBS__ = mo; }
}
function enableAlertInlineReply() {
if (!on(LS.reply)) return;
window.__LL_ALERTS_DISABLED__ = false;
if (window.__LL_ALERTS_WIRED__) return;
window.__LL_ALERTS_WIRED__ = true;
ensureInlineReplyStyles();
injectReplyButtonsForAlerts();
if (!window.__LL_REPLY_CLICK_FN__) {
const handler = (e) => {
const btn = e.target.closest?.('.ll-reply-btn');
if (!btn) return;
e.preventDefault();
e.stopImmediatePropagation?.();
e.stopPropagation();
const li = btn.closest('li.Alert');
if (li) showInlineReply(li);
};
['click','mousedown','pointerdown','touchstart'].forEach(ev=>{
document.addEventListener(ev, handler, true);
});
window.__LL_REPLY_CLICK_FN__ = handler;
}
if (!window.__LL_ALERTS_OBS__) {
const run = () => injectReplyButtonsForAlerts();
const panel = document.getElementById('AlertPanels');
if (panel) new MutationObserver(run).observe(panel, { childList: true, subtree: true });
new MutationObserver((m, ob) => {
if (document.getElementById('AlertPanels')) { run(); ob.disconnect(); }
}).observe(document.body, { childList: true, subtree: true });
document.addEventListener('click', e => {
if (e.target.closest?.('#ConversationsMenu, #AlertsMenu, .navLink.alerts')) run();
}, true);
window.__LL_ALERTS_OBS__ = 1;
}
function normalizeFromBB(bb){
if(!bb) return '';
return bb
.replace(/\[quote[^\]]*\][\s\S]*?\[\/quote\]/gi,'')
.replace(/^\s*\[user[^\]]*\][^\[]+\[\/user\][,\s\u00A0]*/i,'')
.replace(/\[\/?[\w\-]+(?:=[^\]]*)?\]/g,'')
.replace(/<[^>]*>/g,'')
.replace(/"/g,'"').replace(/&/g,'&')
.replace(/</g,'<').replace(/>/g,'>')
.replace(/\s+/g,' ').trim();
}
}
function ensureInlineReplyStyles(){
if (document.getElementById('ll-inline-reply-css')) return;
const st=document.createElement('style');
st.id='ll-inline-reply-css';
st.textContent = `
.ll-reply-wrap{ margin-top:8px; }
.ll-reply-TA{ width:100%; min-height:64px; }
.ll-reply-actions{ margin-top:6px; display:flex; gap:8px; align-items:center; }
.ll-reply-btn{ padding:4px 8px; font-size:12px; border-radius:6px; border:1px solid #444; background:#303030; color:#ddd; cursor:pointer;}
.ll-reply-btn:hover{ background:#383838; }
.ll-inline-btn {padding:4px 8px; font-size:12px; border-radius:6px; border:1px solid #444; background:#303030; color:#ddd; cursor:pointer; margin-left:6px;}
.ll-inline-btn:hover {background:#383838;}
`;
document.head.appendChild(st);
}
function injectReplyButtonsForAlerts() {
if (!on(LS.reply) || window.__LL_ALERTS_DISABLED__) return;
const items = document.querySelectorAll(
'#AlertsDestinationWrapper li.Alert:not([data-ll-reply-btn]), ' +
'#AccountMenu li.Alert:not([data-ll-reply-btn]), ' +
'li.Alert:not([data-ll-reply-btn])'
);
items.forEach(li => {
li.setAttribute('data-ll-reply-btn', '1');
let bar = li.querySelector('.ll-reply-bar');
if (!bar) {
bar = document.createElement('div');
bar.className = 'll-reply-bar';
const bottom = li.querySelector('.bottom') || li.querySelector('.listItemText') || li;
bottom.appendChild(bar);
}
if (!li.querySelector('.ll-reply-btn')) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'll-reply-btn';
btn.textContent = 'Ответить';
bar.appendChild(btn);
}
});
}
function showInlineReply(li) {
if (li.querySelector('.ll-reply-wrap')) return;
const bar = li.querySelector('.ll-reply-bar') || li;
const wrap = document.createElement('div');
wrap.className = 'll-reply-wrap';
wrap.innerHTML = `
<div class="ll-reply-box">
<textarea class="ll-reply-TA" placeholder="Ваш ответ…" rows="3"></textarea>
<div class="ll-reply-actions">
<button class="ll-reply-send" type="button">Отправить</button>
<button class="ll-reply-cancel" type="button">Отмена</button>
<span class="ll-reply-msg" style="font-size:12px;opacity:.8"></span>
</div>
</div>
`;
['click', 'mousedown', 'mouseup', 'pointerdown', 'pointerup', 'touchstart', 'touchend'].forEach(ev => {
wrap.addEventListener(ev, ev2 => ev2.stopPropagation(), true);
});
if (bar.after) bar.after(wrap); else bar.appendChild(wrap);
const ta = wrap.querySelector('.ll-reply-TA');
const send = wrap.querySelector('.ll-reply-send');
const cancel = wrap.querySelector('.ll-reply-cancel');
const msg = wrap.querySelector('.ll-reply-msg');
ta.focus();
cancel.addEventListener('click', (e) => {
e.preventDefault(); e.stopPropagation();
wrap.remove();
});
send.addEventListener('click', (e) => {
e.preventDefault(); e.stopPropagation();
doSend(ta, send, msg, li, wrap);
});
ta.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); e.stopPropagation();
doSend(ta, send, msg, li, wrap);
} else if (e.key === 'Escape') {
e.preventDefault(); e.stopPropagation();
wrap.remove();
}
});
}
function getAuthorFromAlert(li) {
const name = (li.getAttribute('data-author') || li.querySelector('.username')?.textContent || '').trim();
let id = '';
const a = li.querySelector('a[href^="members/"]');
if (a) {
const m = a.getAttribute('href').match(/members\/(\d+)\//);
if (m) id = m[1];
}
return { id, name };
}
function buildUserPrefix(li) {
const { id, name } = getAuthorFromAlert(li);
if (id && name) return `[USER=${id}]${name}[/USER], `;
if (name) return `${name}, `;
return '';
}
function getAlertKind(li){
const t = (li.querySelector('h3')?.textContent || '').toLowerCase();
if (t.includes('создал') && t.includes('тему')) return 'thread_created';
if (t.includes('ответил') && t.includes('в теме')) return 'replied_in_thread';
return 'other';
}
async function pickAction(li) {
const kind = getAlertKind(li);
const previewA = li.querySelector('.PreviewTooltip[data-previewurl]');
const pv = String(previewA?.getAttribute('data-previewurl') || '');
if (kind === 'thread_created') {
const mt = pv.match(/threads\/(\d+)\/preview/);
if (mt) return { type: 'thread', action: `/threads/${mt[1]}/add-reply` };
const hrefA = li.querySelector('a[href^="posts/"]');
if (hrefA) {
const m = hrefA.getAttribute('href').match(/posts\/(\d+)\//);
if (m) return { type: 'comment', action: `/posts/${m[1]}/comment` };
}
}
const hrefA = li.querySelector('a[href^="posts/"]');
if (hrefA) {
const m = hrefA.getAttribute('href').match(/posts\/(\d+)\//);
if (m) return { type: 'comment', action: `/posts/${m[1]}/comment` };
}
const mt = pv.match(/threads\/(\d+)\/preview/);
if (mt) {
const pid = await getFirstPostIdFromThread(mt[1]);
return { type: 'comment', action: `/posts/${pid}/comment` };
}
const postId = await getPostIdFromAlert(li);
return { type: 'comment', action: `/posts/${postId}/comment` };
}
async function doSend(ta, send, msg, li, wrap) {
const text = ta.value.trim();
if (!text) { msg.textContent = 'Введите текст'; return; }
msg.textContent = 'Отправка…';
send.disabled = true;
try {
const form = document.querySelector('#QuickReply');
const { _xfToken, last_date, last_known_date } = getCsrfLikeFields(form);
const { action, type } = await pickAction(li);
const prefix = (type === 'comment') ? buildUserPrefix(li) : '';
const html = prefix ? `<p>${prefix}${text}</p>` : `<p>${text}</p>`;
const fd = new FormData();
fd.append('message_html', html);
fd.append('_xfToken', _xfToken);
fd.append('last_date', last_date);
fd.append('last_known_date', last_known_date);
fd.append('_xfRequestUri', location.pathname + location.search);
fd.append('_xfWithData', '1');
fd.append('_xfResponseType', 'json');
const res = await fetch(action, {
method: 'POST',
body: fd,
credentials: 'include',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/javascript, */*; q=0.01' }
});
const ct = res.headers.get('content-type') || '';
const raw = await res.text();
if (!ct.includes('application/json')) throw new Error(`Не JSON (${res.status}). ${raw.slice(0,200)}…`);
const j = JSON.parse(raw);
if (j._redirectStatus || j.status === 'ok' || j.success) {
msg.textContent = 'Отправлено';
setTimeout(() => wrap.remove(), 400);
return;
}
const err = (j.errors && (Array.isArray(j.errors) ? j.errors.join('; ') : String(j.errors))) || 'Ошибка ответа';
throw new Error(err);
} catch (e) {
msg.textContent = e.message || String(e);
send.disabled = false;
}
}
function enableMenuEditor(){
if(!on(LS.menu)) return;
const KEY='manageItemsData_vFinal';
const qs=(s,c=document)=>c.querySelector(s); const qsa=(s,c=document)=>[...c.querySelectorAll(s)];
const norm=h=>{ try{const u=new URL(h,location.origin); u.searchParams.delete('_xfToken'); return u.href;}catch{return h;} };
const load=()=>{ try{return JSON.parse(localStorage.getItem(KEY))||{order:[],hidden:[],custom:[]};}catch{return{order:[],hidden:[],custom:[]}} };
const save=d=>localStorage.setItem(KEY,JSON.stringify(d));
function initDnd(container){
const items=qsa('.manageItem',container); items.forEach(i=>{i.draggable=true; i.style.cursor='grab';});
let drag=null;
function start(e){ drag=this; e.dataTransfer.effectAllowed='move'; e.dataTransfer.setData('text/plain',norm(this.href)); this.classList.add('dragging'); }
function end(){ this.classList.remove('dragging'); }
function after(cont,y){ const els=qsa('.manageItem',cont).filter(i=>!i.classList.contains('dragging'));
return els.reduce((c,ch)=>{ const b=ch.getBoundingClientRect(); const off=y-b.top-b.height/2; return (off<0&&off>c.offset)?{offset:off,element:ch}:c; },{offset:-Infinity}).element; }
function over(e){ e.preventDefault(); const a=after(container,e.clientY); if(!a) container.appendChild(drag); else container.insertBefore(drag,a); }
function drop(e){ e.preventDefault(); const d=load(); d.order=qsa('.manageItem').map(el=>norm(el.href)); save(d); }
items.forEach(i=>{ i.addEventListener('dragstart',start); i.addEventListener('dragend',end); i.addEventListener('dragover',over); i.addEventListener('drop',drop); });
}
function rebuild(){
const cont=qs('.manageItems'); if(!cont) return;
const d=load();
const native=qsa('.manageItem',cont).filter(el=>!el.dataset.custom).map(el=>({el,href:norm(el.href)})).filter(it=>!d.hidden.includes(it.href));
const customs=d.custom.map(c=>{ const a=document.createElement('a'); a.className='manageItem'; a.href=c.href; a.dataset.custom='1';
a.innerHTML=`<div class="SvgIcon duotone"><svg width="20" height="20" fill="currentColor"><path d="${c.icon}"/></svg></div><span>${c.text}</span>`; return {el:a,href:c.href};});
const all=[...native,...customs]; const order={}; d.order.forEach((h,i)=>order[h]=i);
all.sort((a,b)=>(order[a.href]??999)-(order[b.href]??999));
cont.innerHTML=''; all.forEach(it=>cont.appendChild(it.el)); initDnd(cont);
}
function removeHref(h){ const k=norm(h); const d=load(); if(!d.hidden.includes(k)) d.hidden.push(k); save(d); rebuild(); }
function removeCustom(h){ const k=norm(h); const d=load(); d.custom=d.custom.filter(c=>c.href!==k); save(d); rebuild(); }
function toggleEdit(){
const cont=document.querySelector('.manageItems'); if(!cont) return;
const onEdit=cont.classList.toggle('editMode');
if(onEdit){
qsa('.manageItem',cont).forEach(a=>{ const x=document.createElement('span'); x.innerHTML='×'; x.className='itemCloser';
x.onclick=e=>{ e.preventDefault(); (a.dataset.custom?removeCustom:removeHref)(a.href); }; a.style.position='relative'; a.appendChild(x); });
const plus=document.getElementById('addCustomItemBtn'); if(plus) plus.style.display='flex';
} else {
document.querySelectorAll('.itemCloser').forEach(x=>x.remove());
const plus=document.getElementById('addCustomItemBtn'); if(plus) plus.style.display='none';
}
}
function showAdd(){
if(document.getElementById('customItemOverlay')) return;
const ov=document.createElement('div'); ov.id='customItemOverlay'; ov.className='xenOverlay formOverlay'; ov.style.display='block';
const form=document.createElement('form'); form.className='xenForm'; form.id='customItemForm';
const fs=document.createElement('fieldset');
const row=(l,id,ph='')=>{ const dl=document.createElement('dl'); dl.className='ctrlUnit';
const dt=document.createElement('dt'); const lab=document.createElement('label'); lab.setAttribute('for',id); lab.textContent=l; dt.appendChild(lab);
const dd=document.createElement('dd'); const inp=document.createElement('input'); inp.type='text'; inp.id=id; inp.className='textCtrl OptOut'; inp.placeholder=ph; dd.appendChild(inp);
dl.appendChild(dt); dl.appendChild(dd); return dl; };
fs.appendChild(row('Адрес:','ctrl_custom_url','forums/585/'));
fs.appendChild(row('Название:','ctrl_custom_text','Мой пункт'));
fs.appendChild(row('SVG-иконка (path):','ctrl_custom_icon','M4 6h16M4 12h16M4 18h16'));
form.appendChild(fs);
const footer=document.createElement('div'); footer.className='sectionFooter';
const saveBtn=document.createElement('input'); saveBtn.type='submit'; saveBtn.value='Сохранить'; saveBtn.className='button primary';
const cancelBtn=document.createElement('input'); cancelBtn.type='button'; cancelBtn.value='Отмена'; cancelBtn.className='button';
footer.appendChild(saveBtn); footer.appendChild(cancelBtn); form.appendChild(footer); ov.appendChild(form); document.body.appendChild(ov);
const close=()=>ov.remove(); cancelBtn.onclick=close;
form.onsubmit=e=>{ e.preventDefault();
const url=document.getElementById('ctrl_custom_url').value.trim();
const text=document.getElementById('ctrl_custom_text').value.trim();
const icon=document.getElementById('ctrl_custom_icon').value.trim();
if(!url||!text){ alert('Заполни адрес и название!'); return; }
const abs = (()=>{ try{ return new URL(url,location.origin).href; }catch{ return location.origin+'/'+url.replace(/^\/+/,''); }})();
const d=load(); const i=d.custom.findIndex(c=>c.href===abs); if(i!==-1) d.custom[i]={href:abs,text,icon}; else d.custom.push({href:abs,text,icon});
save(d); rebuild(); close();
};
}
function init(){
const cont=document.querySelector('.manageItems'); if(!cont) return;
rebuild();
const bar=document.createElement('div'); bar.className='editTriggerBar';
bar.innerHTML=`<svg width="24" height="24" fill="#888"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.65-.07-.97l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.488.488 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0 -.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.32-.07.65-.07.97 0 .33.03.65.07.97L2.46 14.6c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.31.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18 .49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23-.09 .49 0 .61-.22l2-3.46c.12-.22 .07-.49-.12-.64l-2.11-1.66Z"/></svg>`;
bar.style.cssText='text-align:center;padding:6px 0;cursor:pointer;opacity:.6;transition:opacity .2s'; bar.title='Редактировать пункты';
bar.onmouseenter=()=>bar.style.opacity=1; bar.onmouseleave=()=>bar.style.opacity=.6; bar.onclick=()=>toggleEdit();
cont.parentElement.insertBefore(bar,cont.nextSibling);
const plus=document.createElement('a'); plus.className='manageItem addCustomItem'; plus.id='addCustomItemBtn'; plus.href='javascript:;';
plus.innerHTML=`<div class="SvgIcon duotone"><svg width="24" height="24" fill="currentColor"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2Z"/></svg></div><span>Добавить свой пункт</span>`;
plus.style.display='none'; cont.parentElement.insertBefore(plus,cont.nextSibling); plus.addEventListener('click',showAdd);
}
new MutationObserver((_,ob)=>{ if(document.querySelector('.manageItems')){ init(); ob.disconnect(); } }).observe(document,{childList:true,subtree:true});
}
function enableCopyLinkInMenu(){
if (!on(LS.copy)) return;
if (window.__LL_COPY_MENU_WIRED__) return;
window.__LL_COPY_MENU_WIRED__ = true;
const ICON = `
<span class="Svg-Icon ico">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 3h8a3 3 0 0 1 3 3v8h-2V6a1 1 0 0 0-1-1H9V3Zm-4 4h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V10a3 3 0 0 1 3-3Zm0 2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1H5Z"/></svg>
</span>`;
const mo = new MutationObserver(muts=>{
let need=false;
for(const m of muts){
if(m.type==='attributes' && m.target.classList && m.target.classList.contains('Menu')) need=true;
if(m.addedNodes && m.addedNodes.length) need=true;
}
if(need) augmentMenus();
});
mo.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class']});
window.__LL_COPY_MENU_OBS__ = mo;
augmentMenus();
document.addEventListener('mousedown', (e)=>{
if (e.target && !(e.target.closest && e.target.closest('.Menu'))) window.__LL_LAST_CLICK = e.target;
}, true);
document.addEventListener('touchstart', (e)=>{
const t = e.targetTouches && e.targetTouches[0] && e.targetTouches[0].target;
if (t && !(t.closest && t.closest('.Menu'))) window.__LL_LAST_CLICK = t;
}, true);
document.addEventListener('click', async (e)=>{
const a = e.target.closest('a.ll-copy-link, a.ll-copy-text');
if(!a) return;
e.preventDefault(); e.stopPropagation();
const menu = a.closest('.Menu');
if (a.classList.contains('ll-copy-link')) {
const url = getPostUrlFromNode(menu) || getPostUrlFromNode(document);
a.title = (url && await copyText(url)) ? 'Ссылка скопирована' : 'Не удалось скопировать ссылку';
return;
}
const ids = getIdsFromMenu(menu);
let text = '';
if (ids) {
const url = ids.type==='comment'
? `/posts/comments/${ids.id}/get-copy-text`
: `/posts/${ids.id}/get-copy-text`;
try{
const res = await fetch(url, {credentials:'include', headers:{'X-Requested-With':'XMLHttpRequest'}});
let raw = await res.text();
const ct = (res.headers.get('content-type')||'').toLowerCase();
const looksHtml = /<!doctype|<html|<body|<form|<div/i.test(raw);
if (!ct.includes('text/plain') && looksHtml) {
raw = '';
}
if (raw) text = normalizeFromBB(raw);
}catch{}
}
if (!text) {
const origin = window.__LL_LAST_CLICK && !window.__LL_LAST_CLICK.closest?.('.Menu') ? window.__LL_LAST_CLICK : null;
const fromOrigin = origin ? findMessageContainer(origin) : null;
const target = fromOrigin || resolveMenuTarget(menu);
text = target ? getPostPlainText(target) : '';
}
a.title = (text && await copyText(text)) ? 'Текст скопирован' : 'Не удалось скопировать текст';
}, true);
function augmentMenus(){
document.querySelectorAll('.Menu.MenuOpened .blockLinksList.have-icon:not([data-ll-copy-wired])').forEach(list=>{
list.dataset.llCopyWired='1';
if (!list.querySelector('.ll-copy-text')) {
const a2=document.createElement('a');
a2.href='javascript:;'; a2.className='item control ll-copy-text';
a2.innerHTML = `${ICON}Скопировать сообщение`;
list.insertBefore(a2, list.firstChild);
}
if (!list.querySelector('.ll-copy-link')) {
const a1=document.createElement('a');
a1.href='javascript:;'; a1.className='item control ll-copy-link';
a1.innerHTML = `${ICON}Скопировать ссылку`;
list.insertBefore(a1, list.firstChild);
}
});
}
function findMessageContainer(node){
return node.closest('li.message, article.message, .message, li.Comment, li.comment, .comment, [id^="comment-"], [data-commentid], [data-comment-id]');
}
function getIdsFromMenu(menu){
const bag=[...menu.querySelectorAll('a[href], .CopyToClipboard[data-source-href]')]
.map(el=>el.getAttribute('href')||el.getAttribute('data-source-href')||'');
for(const s of bag){
let m = s.match(/posts\/comments\/(\d+)\b/); if (m) return { type: 'comment', id: m[1] };
m = s.match(/posts\/(\d+)\b/); if (m) return { type: 'post', id: m[1] };
}
return null;
}
function resolveMenuTarget(menu){
const clip = menu.querySelector('.CopyToClipboard[data-source-href]');
if (clip){
const src = clip.getAttribute('data-source-href') || '';
let m = src.match(/posts\/(\d+)\b/);
if (m) return document.getElementById(`post-${m[1]}`) || document.querySelector(`#post-${m[1]}`);
m = src.match(/posts\/comments\/(\d+)\b/);
if (m) {
const id=m[1];
return document.getElementById(`post-comment-${id}`) ||
document.getElementById(`comment-${id}`) ||
document.querySelector(`#post-comment-${id}, #comment-${id}, li[id^="comment-"][id$="${id}"], li.Comment[data-commentid="${id}"], li.comment[data-comment-id="${id}"]`);
}
}
let any = menu.querySelector('a[href*="posts/comments/"]');
if (any){
const m = any.getAttribute('href').match(/posts\/comments\/(\d+)\b/);
if (m){
const id=m[1];
return document.getElementById(`post-comment-${id}`) ||
document.querySelector(`#post-comment-${id}, li.comment[id="post-comment-${id}"], li.comment[id="comment-${id}"]`);
}
}
any = menu.querySelector('a[href*="posts/"]');
if (any){
const m = any.getAttribute('href').match(/posts\/(\d+)\b/);
if (m){
const id=m[1];
return document.getElementById(`post-${id}`) || document.querySelector(`#post-${id}`);
}
}
return null;
}
function normalizeFromBB(bb){
if(!bb) return '';
return bb
.replace(/\[quote[^\]]*\][\s\S]*?\[\/quote\]/gi,'')
.replace(/^\s*\[user[^\]]*\][^\[]+\[\/user\][,\s\u00A0]*/i,'')
.replace(/\[\/?[\w\-]+(?:=[^\]]*)?\]/g,'')
.replace(/"/g,'"').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
.replace(/\s+/g,' ').trim();
}
}
let __LL_SND_INIT__ = false;
let __LL_SND_LAST__ = { alerts: null, msgs: null };
let __LL_SND_TMO__ = null;
let __LL_SND_THROTTLE__ = 0;
function getBadgeCount(sel){
const el = document.querySelector(sel);
if (!el) return 0;
const n = Number(el.textContent.replace(/\D+/g,''));
return Number.isFinite(n) ? n : 0;
}
function makeAudio(dataKey){
const data = localStorage.getItem(dataKey);
if (!data) return null;
try{
const a = new Audio(data);
a.preload = 'auto';
return a;
}catch{ return null; }
}
function playOnce(a){
const now = Date.now();
if (now - __LL_SND_THROTTLE__ < 2000) return;
__LL_SND_THROTTLE__ = now;
a && a.play?.().catch(()=>{});
}
function enableSoundSystem(){
if (__LL_SND_INIT__) return;
__LL_SND_INIT__ = true;
if (__LL_SND_TMO__) { clearInterval(__LL_SND_TMO__); __LL_SND_TMO__ = null; }
__LL_SND_LAST__.alerts = getBadgeCount('.navLink.alerts .itemCount, #AlertsMenu_Counter .itemCount, #AlertsMenu_Counter');
__LL_SND_LAST__.msgs = getBadgeCount('.navLink.conversations .itemCount, #ConversationsMenu_Counter .itemCount, #ConversationsMenu_Counter');
__LL_SND_TMO__ = setInterval(()=>{
if (on(LS.soundAlerts)) {
const cur = getBadgeCount('.navLink.alerts .itemCount, #AlertsMenu_Counter .itemCount, #AlertsMenu_Counter');
if (__LL_SND_LAST__.alerts != null && cur > __LL_SND_LAST__.alerts) {
const a = makeAudio(LS.soundAlertsData); if (a) playOnce(a);
}
__LL_SND_LAST__.alerts = cur;
}
if (on(LS.soundMsgs)) {
const cur = getBadgeCount('.navLink.conversations .itemCount, #ConversationsMenu_Counter .itemCount, #ConversationsMenu_Counter');
if (__LL_SND_LAST__.msgs != null && cur > __LL_SND_LAST__.msgs) {
const a = makeAudio(LS.soundMsgsData); if (a) playOnce(a);
}
__LL_SND_LAST__.msgs = cur;
}
if (!on(LS.soundAlerts) && !on(LS.soundMsgs)) {
disableSoundSystemIfNone();
}
}, 1500);
}
function disableSoundSystemIfNone(){
if (on(LS.soundAlerts) || on(LS.soundMsgs)) return;
__LL_SND_INIT__ = false;
if (__LL_SND_TMO__) { clearInterval(__LL_SND_TMO__); __LL_SND_TMO__ = null; }
}
LS.alertsSort = LS.alertsSort || 'll_enable_alerts_sort';
function llClearAlertsSubfilter(){
localStorage.setItem('ll_alerts_sort_mode', '');
document.querySelectorAll('.alertsTabsWrapper ul.alertsTabs')
.forEach(ul=> setSubtabState(ul, ''));
applyAlertsFilter();
}
function disableAlertsSorting(){
document.querySelectorAll('.alertsTabsWrapper ul.alertsTabs li[data-ll-sub="1"]').forEach(li=>li.remove());
document.querySelectorAll('#AlertPanels li.Alert[style]').forEach(li=>li.style.display='');
const mo = window.__LL_ALERTS_SORT_OBS__;
if (mo && mo.disconnect) mo.disconnect();
window.__LL_ALERTS_SORT_OBS__ = null;
}
function setSubtabState(ul, mode){
ul.querySelectorAll('li[data-ll-sub="1"]').forEach(li=>li.classList.remove('ll-on'));
const base = ul.querySelectorAll('li:not([data-ll-sub])');
if (mode==='likes' || mode==='replies'){
const txt = mode==='likes' ? 'Симпатии/лайки' : 'Ответы';
const li = Array.from(ul.querySelectorAll('li[data-ll-sub="1"]')).find(n=>n.textContent.trim()===txt);
if (li) li.classList.add('ll-on');
base.forEach(li=>li.classList.add('ll-dim'));
} else {
base.forEach(li=>li.classList.remove('ll-dim'));
}
}
function applyAlertsFilter(){
const mode = localStorage.getItem('ll_alerts_sort_mode') || '';
document.querySelectorAll('#AlertPanels li.Alert').forEach(li=>{
const act = li.querySelector('.alertAction');
const c = act ? act.classList : null;
const isLike = !!(c && (c.contains('like') || c.contains('like2')));
const isReply = !!(c && (c.contains('insert') || c.contains('your_post') || c.contains('tag')));
let show = true;
if (mode==='likes') show = isLike;
else if (mode==='replies') show = isReply;
li.style.display = show ? '' : 'none';
});
}
function ensureAlertSortStyles(){
if (document.getElementById('ll-alerts-sort-css')) return;
const st = document.createElement('style');
st.id = 'll-alerts-sort-css';
st.textContent = `
.alertsTabsWrapper{ position:relative; margin:0 -16px; padding:0; }
.navPopup .alertsTabs {
width: calc(100% - 15px) !important;
}
.alertsTabsWrapper ul.alertsTabs{
display:flex; flex-wrap:nowrap; gap:10px;
overflow-x:hidden; overflow-y:hidden;
white-space:nowrap; scroll-behavior:smooth;
margin:0; padding:0 16px;
justify-content:flex-start;
}
.alertsTabsWrapper ul.alertsTabs::-webkit-scrollbar{ display:none; }
.alertsTabsWrapper .tabs li,
.alertsTabsWrapper .tabs li > a{
max-width:none !important;
min-width:max-content !important;
width:auto !important;
flex:0 0 auto !important;
white-space:nowrap !important;
overflow:visible !important;
text-overflow:clip !important;
}
.ll-tabs-scroll{
position:absolute; top:50%; transform:translateY(-50%);
width:26px; height:26px; border:1px solid #444; border-radius:50%;
background:#2d2d2d; color:#ddd; display:flex; align-items:center; justify-content:center;
cursor:pointer; z-index:2; opacity:.85;
}
.ll-tabs-scroll:hover{ background:#333; }
.ll-tabs-left{ left:20px; }
.ll-tabs-right{ right:20px; }
`;
document.head.appendChild(st);
const wrapper = document.querySelector('.alertsTabsWrapper');
if (!wrapper) return;
const ul = wrapper.querySelector('ul.alertsTabs');
const btnLeft = wrapper.querySelector('.ll-tabs-left');
const btnRight = wrapper.querySelector('.ll-tabs-right');
if (!ul) return;
ul.querySelectorAll('li').forEach(li => li.removeAttribute('style'));
const step = () => Math.floor(ul.offsetWidth * 0.7);
if (btnLeft) btnLeft.onclick = () => ul.scrollBy({ left: -step(), behavior: 'smooth' });
if (btnRight) btnRight.onclick = () => ul.scrollBy({ left: step(), behavior: 'smooth' });
}
function wireTabsScroller(ul){
if (!ul || ul.dataset.llScrollInit === '1') return;
const wrap = ul.closest('.alertsTabsWrapper') || ul.parentElement;
if (!wrap) return;
const makeBtn = (cls, svgPath) => {
const b = document.createElement('button');
b.type = 'button'; b.className = `ll-tabs-scroll ${cls}`;
b.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="${svgPath}"/></svg>`;
wrap.appendChild(b);
return b;
};
const left = makeBtn('ll-tabs-left', 'M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z');
const right = makeBtn('ll-tabs-right','M8.59 16.59 10 18l6-6-6-6-1.41 1.41L13.17 12z');
const step = () => Math.max(160, Math.round(ul.clientWidth * 0.6));
left.onclick = () => ul.scrollBy({ left: -step(), behavior: 'smooth' });
right.onclick = () => ul.scrollBy({ left: step(), behavior: 'smooth' });
const update = () => {
const max = Math.max(0, ul.scrollWidth - ul.clientWidth);
const x = Math.round(ul.scrollLeft);
if (max <= 0) {
left.style.display = 'none';
right.style.display = 'none';
return;
}
left.style.display = x > 0 ? 'flex' : 'none';
right.style.display = x < max - 1 ? 'flex' : 'none';
};
ul.addEventListener('scroll', update, { passive:true });
window.addEventListener('resize', update);
ul.scrollLeft = 0;
setTimeout(update, 0);
ul.dataset.llScrollInit = '1';
}
function unclipAlertTabs(ul){
if (!ul) return;
const setImp = (el, prop, val) => el && el.style.setProperty(prop, val, 'important');
ul.querySelectorAll('li').forEach(li=>{
setImp(li, 'max-width', 'none');
setImp(li, 'min-width', 'max-content');
setImp(li, 'width', 'auto');
setImp(li, 'flex', '0 0 auto');
setImp(li, 'overflow', 'visible');
setImp(li, 'text-overflow', 'clip');
setImp(li, 'white-space', 'nowrap');
const a = li.firstElementChild;
if (a && a.tagName === 'A'){
setImp(a, 'max-width', 'none');
setImp(a, 'min-width', 'max-content');
setImp(a, 'width', 'auto');
setImp(a, 'overflow', 'visible');
setImp(a, 'text-overflow', 'clip');
setImp(a, 'white-space', 'nowrap');
setImp(a, 'display', 'inline-block');
}
});
}
function enableAlertsSorting(){
if (!on(LS.alertsSort)) return;
ensureAlertSortStyles();
document.querySelectorAll('.alertsTabsWrapper ul.alertsTabs').forEach(ul=>{
if (ul.dataset.llSortInit === '1') return;
ul.dataset.llSortInit = '1';
const liLikes = document.createElement('li');
liLikes.textContent = 'Симпатии/лайки';
liLikes.dataset.llSub = '1';
const liReplies = document.createElement('li');
liReplies.textContent = 'Ответы';
liReplies.dataset.llSub = '1';
const onClick = (mode) => (e) => {
e.preventDefault(); e.stopPropagation();
localStorage.setItem('ll_alerts_sort_mode', mode);
setSubtabState(ul, mode);
unclipAlertTabs(ul);
wireTabsScroller(ul);
applyAlertsFilter();
};
liLikes.addEventListener('click', onClick('likes'));
liReplies.addEventListener('click', onClick('replies'));
ul.appendChild(liLikes);
ul.appendChild(liReplies);
ul.querySelectorAll('li:not([data-ll-sub])').forEach(li => li.dataset.llBase = '1');
setSubtabState(ul, localStorage.getItem('ll_alerts_sort_mode') || '');
unclipAlertTabs(ul);
setTimeout(()=>unclipAlertTabs(ul), 0);
wireTabsScroller(ul);
});
applyAlertsFilter();
if (!window.__LL_ALERTS_SORT_OBS__){
const run = () => {
applyAlertsFilter();
document.querySelectorAll('.alertsTabsWrapper ul.alertsTabs').forEach(ul=>{
unclipAlertTabs(ul);
wireTabsScroller(ul);
});
};
const panel = document.getElementById('AlertPanels');
if (panel){
const mo = new MutationObserver(run);
mo.observe(panel, { childList:true, subtree:true });
window.__LL_ALERTS_SORT_OBS__ = mo;
}
document.addEventListener('click', e=>{
const baseTab = e.target.closest?.('.alertsTabsWrapper .alertsTabs li:not([data-ll-sub])');
if (baseTab) llClearAlertsSubfilter();
if (baseTab || e.target.closest?.('#AlertsMenu, .navLink.alerts')) run();
}, true);
}
};
(function(){ if(on(LS.bgEnable)){ const d=localStorage.getItem(LS.bgData); if(d) applyBg(d); } })();
let tries=0;
const t=setInterval(()=>{
const ok=isPrefs()?injectPrefs():true;
if(ok){
safe(addCategoryToggles);
safe(enableAlertsCollapse);
safe(enableMenuEditor);
if (on(LS.alertsSort)) safe(enableAlertsSorting); else safe(disableAlertsSorting);
if (on(LS.reply)) safe(enableAlertInlineReply);
else disableAlertInlineReply && disableAlertInlineReply();
if (on(LS.soundAlerts) || on(LS.soundMsgs)) safe(enableSoundSystem);
safe(enableFullTimeDates);
safe(enableHideHotThreads);
safe(enableCopyLinkInMenu);
clearInterval(t);
}
if(++tries>40) clearInterval(t);
},250);
})();