diff --git a/omni/store.py b/omni/store.py index 1e865bf..83ada87 100644 --- a/omni/store.py +++ b/omni/store.py @@ -216,9 +216,22 @@ def browse2(q="", names=None, limit=60, offset=0, sort="score", priced=False, ty if typ and typ != "all": where += "AND (',' || COALESCE(types,'') || ',') LIKE ? " args += [f"%,{typ},%"] + if sort == "quality": + # Чистая лента: выкидываем шум до сортировки — длинные склейки-«лапши», + # короткие хайповые куски, сервисный мусор (wiki/docs/changelog/chapterlist), + # и записи без даты и без текста. + where += """AND length(COALESCE(title,'')) BETWEEN 8 AND 220 + AND title NOT GLOB '*[[]Chapter List*' AND title NOT LIKE '%||%||%' + AND lower(COALESCE(title,'')) NOT LIKE '%changelog%' + AND lower(COALESCE(title,'')) NOT LIKE '%release notes%' + AND lower(COALESCE(title,'')) NOT LIKE '%documentation%' + AND (COALESCE(text,'') = '' OR length(text) <= 4000 OR length(title) >= 25) + AND (published IS NOT NULL AND published != '' OR length(COALESCE(text,'')) >= 60) + """ order = {"fresh": "ORDER BY published DESC, id DESC", "price": "ORDER BY CAST(price AS REAL) DESC, score DESC", - "old": "ORDER BY published ASC, id ASC"}.get(sort, "ORDER BY score DESC, id DESC") + "old": "ORDER BY published ASC, id ASC", + "quality": "ORDER BY score DESC, id DESC"}.get(sort, "ORDER BY score DESC, id DESC") with db() as c: total = c.execute("SELECT COUNT(*) FROM items " + where, args).fetchone()[0] rows = c.execute("SELECT id,provider,kind,source,title,text,author,published,score,price,currency,url,types " diff --git a/web/index.html b/web/index.html index abd57c9..5f0ef9e 100644 --- a/web/index.html +++ b/web/index.html @@ -352,7 +352,7 @@ pre { padding: var(--omni-space-4); } @@ -449,7 +449,7 @@ function requestScope(key,ctx=activeContext){const serial=(serials[key]||0)+1;se function route(){ const [raw,query=""]=location.hash.replace(/^#\/?/,"").split("?"),p=new URLSearchParams(query),before=S.view; S.view=NAV.some(n=>n.id===raw)?raw:"feed";S.q=p.get("q")||"";S.cat=p.get("cat")||"";S.type=p.get("type")||"all"; - S.sort=["fresh","old","score","price"].includes(p.get("sort"))?p.get("sort"):"fresh";S.days=int(p.get("days"),0,3650);S.priced=p.get("priced")==="1";S.src=p.get("src")||"";S.page=int(p.get("page")||1,1,25001);S.tag=p.get("tag")||"";S.tab=p.get("tab")==="panels"?"panels":"signals"; + S.sort=["fresh","old","score","price","quality"].includes(p.get("sort"))?p.get("sort"):"fresh";S.days=int(p.get("days"),0,3650);S.priced=p.get("priced")==="1";S.src=p.get("src")||"";S.page=int(p.get("page")||1,1,25001);S.tag=p.get("tag")||"";S.tab=p.get("tab")==="panels"?"panels":"signals"; closeDrawer();if($("#modal").classList.contains("show"))tabler.Modal.getOrCreateInstance($("#modal")).hide(); if(before!==S.view)$("#global-feedback").innerHTML="";render(before!==S.view); } @@ -489,7 +489,7 @@ function itemRow(it,compact=false){const labels=typeLabels(),types=typesOf(it).s const exQS=()=>new URLSearchParams({q:S.q,category:S.cat,type:S.type,sort:S.sort,...(S.days?{days:String(S.days)}:{}),...(S.priced?{priced:"1"}:{}),...(S.src?{src:S.src}:{}),limit:"1000"}).toString(); function readFeedFilters(){return {q:$("#q")?.value.trim()||"",cat:$("#cat")?.value||"",sort:$("#f-sort")?.value||"fresh",days:+($("#f-days")?.value||0),type:$("#f-type")?.value||"all",src:$("#f-src")?.value.trim()||"",priced:!!$("#f-priced")?.checked,page:1}} V.feed=async ctx=>{ - ctx.mount(`
Дополнительные фильтры
Загрузка записей…
${loading("Загрузка ленты…",6)}
`); + ctx.mount(`
Дополнительные фильтры
Загрузка записей…
${loading("Загрузка ленты…",6)}
`); await loadItems(ctx); }; async function loadItems(ctx=activeContext){const scope=requestScope("feed",ctx),offset=(S.page-1)*40,p=new URLSearchParams({limit:"40",offset:String(offset),q:S.q,sort:S.sort,type:S.type,...(S.cat?{category:S.cat}:{}),...(S.days?{days:String(S.days)}:{}),...(S.priced?{priced:"1"}:{}),...(S.src?{src:S.src}:{})}); @@ -563,7 +563,7 @@ const ACT={ run:el=>startJob("/api/feed",{category:S.cat||null,limit:12},"feed",el,"Сбор записей"), "page-prev":()=>go("feed",{page:Math.max(1,S.page-1)},true),"page-next":()=>go("feed",{page:S.page+1},true),more:()=>go("feed",{page:S.page+1},true), "clear-search":()=>{S.focusSearch=true;go("feed",{q:""},true)}, - "reset-filters":()=>go("feed",{q:"",cat:"",type:"all",sort:"fresh",days:0,priced:false,src:"",page:1},true), + "reset-filters":()=>go("feed",{q:"",cat:"",type:"all",sort:"quality",days:0,priced:false,src:"",page:1},true), "source-reset":()=>{S.srcQ="";S.srcCat="";S.srcState="";S.srcEnabled="";return render()}, "add-src":addSource,"add-src-save":saveSource, "toggle-all":async el=>{const on=el.dataset.on==="1",category=S.srcCat||null;if(!confirm(`${on?"Включить":"Выключить"} все источники ${category?"категории «"+category+"»":"всего реестра"}? Поиск и фильтр статуса не ограничивают эту операцию.`))return;await busy(el,"source-write",async()=>{const ctx=activeContext,d=await post("/api/sources/toggle_all",{enabled:on,category});toast(`${on?"Включено":"Выключено"}: ${num(d.changed)}`);if(ctx.valid())await loadSources(ctx)})}, @@ -600,6 +600,7 @@ document.addEventListener("click",async event=>{const target=event.target;if(!(t const suggestion=target.closest("[data-sug-id],[data-sug-src],[data-sug-q]");if(suggestion){hideSuggestions();if(suggestion.dataset.sugId)await openItem(+suggestion.dataset.sugId);else if(suggestion.dataset.sugSrc){S.srcQ=suggestion.dataset.sugSrc;go("sources")}else go("feed",{q:suggestion.dataset.sugQ},true);return} if(!target.closest(".search-wrap"))hideSuggestions(); }); +document.addEventListener("click",e=>{const a=e.target.closest("[data-ext]");if(a){e.preventDefault();window.open(a.dataset.ext,"_blank","noopener")}}); document.addEventListener("change",async event=>{const el=event.target;if(el.matches("#cat,#f-sort,#f-days,#f-priced,#f-type,#f-src")){go("feed",readFeedFilters(),true);return}if(el.id==="ab-tag"){go("abuse",{tag:el.value},true);return} if(el.id==="s-cat"||el.id==="s-state"||el.id==="s-enabled"){S[{"s-cat":"srcCat","s-state":"srcState","s-enabled":"srcEnabled"}[el.id]]=el.value;await loadSources();return} if(el.id==="src-all"){$$(".src-ck").forEach(cb=>cb.checked=el.checked);srcBulkBar();return}if(el.classList.contains("src-ck")){srcBulkBar();return}