feed: сортировка 'Чистая лента' (шум changelog/lапша/wiki выпилен), футер с @AlStack вместо гита
This commit is contained in:
@@ -352,7 +352,7 @@ pre { padding: var(--omni-space-4); }
|
||||
</main>
|
||||
<footer class="footer footer-transparent d-print-none">
|
||||
<div class="container-xl d-flex flex-wrap justify-content-between gap-2 small text-secondary">
|
||||
<span>OMNI · личная рабочая лента</span><span>Данные и сессии остаются на сервере</span>
|
||||
<span>OMNI · личная рабочая лента · <a href="#" data-ext="https://t.me/AlStack" target="_blank" rel="noopener">Канал @AlStack</a></span><span>Данные и сессии остаются на сервере</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -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(`<div class="card"><form id="feed-form" class="card-body border-bottom"><div class="row g-3 align-items-end"><div class="col-12 col-xl-5"><label class="form-label" for="q">Поиск по заголовкам и тексту</label><div class="search-wrap"><div class="input-group"><input class="form-control" id="q" type="search" placeholder="Поиск по заголовкам и тексту" value="${esc(S.q)}" autocomplete="off" aria-controls="sug" aria-expanded="false"><button class="btn" type="button" data-act="clear-search" aria-label="Очистить поисковый запрос">Очистить</button><button class="btn" type="submit">Найти</button></div><div class="dropdown-menu w-100 search-results d-none" id="sug" aria-label="Подсказки поиска"></div></div></div><div class="col-6 col-md-4 col-xl-2"><label class="form-label" for="f-days">Период публикации</label><select class="form-select" id="f-days">${[[0,"За всё время"],[1,"За сутки"],[7,"За неделю"],[30,"За месяц"],[90,"За квартал"],...([0,1,7,30,90].includes(S.days)?[]:[[S.days,`За ${S.days} дн.`]])].map(([v,l])=>`<option value="${v}" ${S.days===v?"selected":""}>${l}</option>`).join("")}</select></div><div class="col-6 col-md-4 col-xl-3"><label class="form-label" for="cat">Категория</label><select class="form-select" id="cat">${categoryOptions(S.cat)}</select></div><div class="col-12 col-md-4 col-xl-2"><label class="form-label" for="f-sort">Сортировка</label><select class="form-select" id="f-sort">${[["fresh","Сначала новые"],["score","По релевантности"],["old","Сначала старые"],["price","По цене"]].map(([v,l])=>`<option value="${v}" ${S.sort===v?"selected":""}>${l}</option>`).join("")}</select></div></div><details class="mt-3" ${S.type!=="all"||S.src||S.priced?"open":""}><summary class="text-secondary">Дополнительные фильтры</summary><div class="row g-3 align-items-end mt-1"><div class="col-sm-4"><label class="form-label" for="f-type">Тип записи</label><select class="form-select" id="f-type">${typeOptions(S.type)}</select></div><div class="col-sm-5"><label class="form-label" for="f-src">Название источника</label><input class="form-control" id="f-src" value="${esc(S.src)}" placeholder="Точное название из реестра"></div><div class="col-sm-3"><label class="form-check mb-2"><input class="form-check-input" type="checkbox" id="f-priced" ${S.priced?"checked":""}><span class="form-check-label">Только с ценой</span></label></div></div></details><div id="meta-error"></div></form><div class="card-body py-3 d-flex flex-wrap gap-3 align-items-center border-bottom"><div id="feed-count" class="text-secondary" aria-live="polite">Загрузка записей…</div><button class="btn btn-link btn-sm p-0" type="button" data-act="reset-filters">Сбросить фильтры</button><div class="d-flex flex-wrap gap-2 ms-sm-auto"><a class="btn btn-sm" id="export-csv" href="/api/export.csv?${exQS()}" download title="Те же фильтры, до 1000 записей">Экспорт CSV</a><a class="btn btn-sm" id="export-json" href="/api/export.json?${exQS()}" download title="Те же фильтры, до 1000 записей">JSON</a></div></div><div id="list" class="list-group list-group-flush">${loading("Загрузка ленты…",6)}</div><div id="more" class="card-footer"></div></div>`);
|
||||
ctx.mount(`<div class="card"><form id="feed-form" class="card-body border-bottom"><div class="row g-3 align-items-end"><div class="col-12 col-xl-5"><label class="form-label" for="q">Поиск по заголовкам и тексту</label><div class="search-wrap"><div class="input-group"><input class="form-control" id="q" type="search" placeholder="Поиск по заголовкам и тексту" value="${esc(S.q)}" autocomplete="off" aria-controls="sug" aria-expanded="false"><button class="btn" type="button" data-act="clear-search" aria-label="Очистить поисковый запрос">Очистить</button><button class="btn" type="submit">Найти</button></div><div class="dropdown-menu w-100 search-results d-none" id="sug" aria-label="Подсказки поиска"></div></div></div><div class="col-6 col-md-4 col-xl-2"><label class="form-label" for="f-days">Период публикации</label><select class="form-select" id="f-days">${[[0,"За всё время"],[1,"За сутки"],[7,"За неделю"],[30,"За месяц"],[90,"За квартал"],...([0,1,7,30,90].includes(S.days)?[]:[[S.days,`За ${S.days} дн.`]])].map(([v,l])=>`<option value="${v}" ${S.days===v?"selected":""}>${l}</option>`).join("")}</select></div><div class="col-6 col-md-4 col-xl-3"><label class="form-label" for="cat">Категория</label><select class="form-select" id="cat">${categoryOptions(S.cat)}</select></div><div class="col-12 col-md-4 col-xl-2"><label class="form-label" for="f-sort">Сортировка</label><select class="form-select" id="f-sort">${[["fresh","Сначала новые"],["score","По релевантности"],["quality","Чистая лента (без мусора)"],["old","Сначала старые"],["price","По цене"]].map(([v,l])=>`<option value="${v}" ${S.sort===v?"selected":""}>${l}</option>`).join("")}</select></div></div><details class="mt-3" ${S.type!=="all"||S.src||S.priced?"open":""}><summary class="text-secondary">Дополнительные фильтры</summary><div class="row g-3 align-items-end mt-1"><div class="col-sm-4"><label class="form-label" for="f-type">Тип записи</label><select class="form-select" id="f-type">${typeOptions(S.type)}</select></div><div class="col-sm-5"><label class="form-label" for="f-src">Название источника</label><input class="form-control" id="f-src" value="${esc(S.src)}" placeholder="Точное название из реестра"></div><div class="col-sm-3"><label class="form-check mb-2"><input class="form-check-input" type="checkbox" id="f-priced" ${S.priced?"checked":""}><span class="form-check-label">Только с ценой</span></label></div></div></details><div id="meta-error"></div></form><div class="card-body py-3 d-flex flex-wrap gap-3 align-items-center border-bottom"><div id="feed-count" class="text-secondary" aria-live="polite">Загрузка записей…</div><button class="btn btn-link btn-sm p-0" type="button" data-act="reset-filters">Сбросить фильтры</button><div class="d-flex flex-wrap gap-2 ms-sm-auto"><a class="btn btn-sm" id="export-csv" href="/api/export.csv?${exQS()}" download title="Те же фильтры, до 1000 записей">Экспорт CSV</a><a class="btn btn-sm" id="export-json" href="/api/export.json?${exQS()}" download title="Те же фильтры, до 1000 записей">JSON</a></div></div><div id="list" class="list-group list-group-flush">${loading("Загрузка ленты…",6)}</div><div id="more" class="card-footer"></div></div>`);
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user