优化瀑布流显示,优化docs和status显示
This commit is contained in:
1 parent
6a843ceafd
commit
53e08901dd
4 files changed
+164
-158
No files matched your search
@@ -22,6 +22,10 @@ def _build_docs_html(*, base_url: str) -> str:
|
||||
"img_pixiv_cat": u("/random?pixiv_cat=1"),
|
||||
"img_pixiv_re": u("/random?pixiv_cat=1&pximg_mirror_host=re"),
|
||||
"img_redirect": u("/random?redirect=1"),
|
||||
"status": u("/status"),
|
||||
"status_json": u("/status.json"),
|
||||
"wtf": u("/wtf"),
|
||||
"wtf_r18": u("/wtf?r18=1"),
|
||||
"json_full": u("/random?format=json"),
|
||||
"json_simple": u("/random?format=simple_json"),
|
||||
"r18_only": u("/random?r18=1"),
|
||||
@@ -35,9 +39,7 @@ def _build_docs_html(*, base_url: str) -> str:
|
||||
"quality_strong": u(
|
||||
"/random?strategy=quality&quality_samples=200&min_pixels=2000000&min_bookmarks=100&illust_type=illust&ai_type=0"
|
||||
),
|
||||
"complex": u(
|
||||
"/random?format=json&r18=1&illust_type=illust&orientation=portrait&min_pixels=2500000&min_bookmarks=25&min_comments=50&included_tags=loli"
|
||||
),
|
||||
"complex": u("/random?r18=1&illust_type=illust&orientation=portrait&min_pixels=2500000&min_bookmarks=2&min_comments=5&included_tags=loli"),
|
||||
"tags_api": u("/tags"),
|
||||
"authors_api": u("/authors"),
|
||||
"swagger": u("/api/docs"),
|
||||
@@ -250,6 +252,8 @@ def _build_docs_html(*, base_url: str) -> str:
|
||||
<p>质量优先随机 + 强力筛选,支持标签/热度/分辨率/R18/AI/作品类型等组合查询。</p>
|
||||
<div class="chips" aria-label="quick-links">
|
||||
<a class="chip" href="{examples["img_default"]}"><strong>/random</strong> 直接出图</a>
|
||||
<a class="chip" href="{examples["wtf"]}"><strong>/wtf</strong> 瀑布流</a>
|
||||
<a class="chip" href="{examples["status"]}"><strong>/status</strong> 运行状态</a>
|
||||
<a class="chip" href="{examples["json_full"]}"><strong>format=json</strong> 返回 JSON</a>
|
||||
<a class="chip" href="{examples["pure_random"]}"><strong>strategy=random</strong> 纯随机</a>
|
||||
<a class="chip" href="{examples["swagger"]}"><strong>/api/docs</strong> Swagger</a>
|
||||
@@ -294,8 +298,24 @@ def _build_docs_html(*, base_url: str) -> str:
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card" style="margin-top: 14px;">
|
||||
<h2>5) 状态页 / 瀑布流</h2>
|
||||
<p><span class="kbd">/status</span> 为公开仪表盘:展示 API 状态、图库概览、/random 请求统计;<span class="kbd">/status.json</span> 为机器可读 JSON。</p>
|
||||
<pre><code>{examples["status"]}
|
||||
{examples["status_json"]}</code></pre>
|
||||
<p><span class="kbd">/wtf</span> 为瀑布流:参数与 <span class="kbd">/random</span> 完全一致(例如 r18/标签/分辨率/热度等),页面内部会自动加上 <span class="kbd">adaptive=1</span> 与 <span class="kbd">redirect=1</span> 来做屏幕适配与稳定链接。</p>
|
||||
<pre><code>{examples["wtf"]}
|
||||
{examples["wtf_r18"]}</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top: 14px;">
|
||||
<h2>筛选语法速记</h2>
|
||||
<div class="note">
|
||||
<strong>标签 AND / OR 速记:</strong>
|
||||
<span class="kbd">AND</span> 用“重复参数”(例如 <span class="kbd">included_tags=a&included_tags=b</span>),
|
||||
<span class="kbd">OR</span> 用同一参数里的 <span class="kbd">|</span>(例如 <span class="kbd">included_tags=a|b</span>)。
|
||||
<span class="muted">如果你的客户端不方便输入 <span class="kbd">|</span>,也可以写成 <span class="kbd">%7C</span>。</span>
|
||||
</div>
|
||||
<div class="table-wrap" role="region" aria-label="filters-table" tabindex="0">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -340,12 +360,16 @@ def _build_docs_html(*, base_url: str) -> str:
|
||||
<td>
|
||||
必须包含的标签:参数之间是 <strong>AND</strong>,单个参数内用 <code>|</code> 表示 <strong>OR</strong>。<br/>
|
||||
例:<code>included_tags=girl|boy&included_tags=white|black</code> 表示 (girl OR boy) AND (white OR black)。<br/>
|
||||
<span class="muted">提示:URL 里的 <code>&</code> 是“参数分隔符”,要写 AND 就重复参数;要写 OR 就在同一参数里用 <code>|</code>。</span><br/>
|
||||
示例:<a href="{examples["tag_loli"]}">{examples["tag_loli"]}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>excluded_tags</code></td>
|
||||
<td>必须不包含的标签(OR)。可重复传参。示例:<code>excluded_tags=AI生成</code></td>
|
||||
<td>
|
||||
必须不包含的标签:任意命中即排除。可重复传参:<code>excluded_tags=a&excluded_tags=b</code>。<br/>
|
||||
<span class="muted"><code>excluded_tags=a|b</code> 等价于排除 a 或 b(效果同上)。</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>min_width</code><br/><code>min_height</code><br/><code>min_pixels</code></td>
|
||||
@@ -390,7 +414,7 @@ def _build_docs_html(*, base_url: str) -> str:
|
||||
|
||||
<section class="card" style="margin-top: 14px;">
|
||||
<h2>复杂示例(组合查询)</h2>
|
||||
<p>示例:R18 + 插画 + 竖图 + 250万像素以上 + 收藏≥25 + 评论≥50 + 标签包含 loli,并返回 JSON。</p>
|
||||
<p>示例:R18 + 插画 + 竖图 + 250万像素以上 + 收藏≥2 + 评论≥5 + 标签包含 loli(直接出图)。</p>
|
||||
<pre><code>{examples["complex"]}</code></pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,8 @@ from app.jobs.enqueue import enqueue_opportunistic_hydrate_metadata
|
||||
router = APIRouter()
|
||||
|
||||
_MAX_TAG_FILTERS = 50
|
||||
_MAX_TAG_OR_TERMS = 20
|
||||
_MAX_TAG_TOTAL_TERMS = 200
|
||||
|
||||
|
||||
@router.get("/images")
|
||||
@@ -87,22 +89,40 @@ async def list_images(
|
||||
if min_width < 0 or min_height < 0 or min_pixels < 0:
|
||||
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported min_*", status_code=400)
|
||||
|
||||
def _parse_tags(values: list[str] | None) -> list[str]:
|
||||
def _parse_tag_filters(values: list[str] | None) -> list[str]:
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for raw in values or []:
|
||||
for part in str(raw).split("|"):
|
||||
name = part.strip()
|
||||
if not name or name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
out.append(name)
|
||||
expr = str(raw or "").strip()
|
||||
if not expr or expr in seen:
|
||||
continue
|
||||
seen.add(expr)
|
||||
out.append(expr)
|
||||
return out
|
||||
|
||||
included = _parse_tags(included_tags)
|
||||
excluded = _parse_tags(excluded_tags)
|
||||
def _validate_tag_filters(values: list[str]) -> None:
|
||||
total_terms = 0
|
||||
for expr in values:
|
||||
parts: list[str] = []
|
||||
seen_terms: set[str] = set()
|
||||
for part in str(expr).split("|"):
|
||||
term = part.strip()
|
||||
if not term or term in seen_terms:
|
||||
continue
|
||||
seen_terms.add(term)
|
||||
parts.append(term)
|
||||
if len(parts) > _MAX_TAG_OR_TERMS:
|
||||
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Too many tag terms in a group", status_code=400)
|
||||
total_terms += len(parts)
|
||||
if total_terms > _MAX_TAG_TOTAL_TERMS:
|
||||
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Too many tag terms", status_code=400)
|
||||
|
||||
included = _parse_tag_filters(included_tags)
|
||||
excluded = _parse_tag_filters(excluded_tags)
|
||||
if len(included) > _MAX_TAG_FILTERS or len(excluded) > _MAX_TAG_FILTERS:
|
||||
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Too many tag filters", status_code=400)
|
||||
_validate_tag_filters(included)
|
||||
_validate_tag_filters(excluded)
|
||||
|
||||
if user_id is not None and int(user_id) <= 0:
|
||||
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported user_id", status_code=400)
|
||||
|
||||
@@ -48,33 +48,6 @@ async def _query_gallery_stats(engine) -> dict[str, Any]:
|
||||
(await conn.exec_driver_sql("SELECT COUNT(*) FROM images WHERE status=1 AND ai_type IS NULL;")).scalar_one()
|
||||
)
|
||||
|
||||
tag_id = (
|
||||
await conn.exec_driver_sql(
|
||||
"SELECT id FROM tags WHERE name = ? COLLATE NOCASE LIMIT 1;",
|
||||
("loli",),
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if tag_id is None:
|
||||
loli_count = 0
|
||||
loli_found = False
|
||||
else:
|
||||
loli_found = True
|
||||
loli_count = int(
|
||||
(
|
||||
await conn.exec_driver_sql(
|
||||
"""
|
||||
SELECT COUNT(DISTINCT it.image_id)
|
||||
FROM image_tags it
|
||||
JOIN images i ON i.id = it.image_id
|
||||
WHERE it.tag_id = ? AND i.status = 1;
|
||||
""".strip(),
|
||||
(int(tag_id),),
|
||||
)
|
||||
).scalar_one()
|
||||
)
|
||||
|
||||
non_loli_count = max(0, int(images_enabled) - int(loli_count))
|
||||
|
||||
return {
|
||||
"gallery": {
|
||||
"images_total": images_total,
|
||||
@@ -85,7 +58,6 @@ WHERE it.tag_id = ? AND i.status = 1;
|
||||
"breakdown": {
|
||||
"r18": {"r18": r18_count, "safe": safe_count, "unknown": r18_unknown},
|
||||
"ai": {"ai": ai_count, "non_ai": non_ai_count, "unknown": ai_unknown},
|
||||
"loli": {"loli": loli_count, "non_loli": non_loli_count, "tag_found": bool(loli_found)},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,6 +81,7 @@ def _build_status_html(*, base_url: str, status_code: int, payload: dict[str, An
|
||||
|
||||
gallery = payload.get("gallery") if isinstance(payload.get("gallery"), dict) else {}
|
||||
images_total = _clamp_int(gallery.get("images_total"))
|
||||
images_enabled = _clamp_int(gallery.get("images_enabled"))
|
||||
illust_total = _clamp_int(gallery.get("illust_total"))
|
||||
authors_total = _clamp_int(gallery.get("authors_total"))
|
||||
|
||||
@@ -124,7 +97,7 @@ def _build_status_html(*, base_url: str, status_code: int, payload: dict[str, An
|
||||
json_url = u("/status.json")
|
||||
docs_url = u("/docs")
|
||||
random_url = u("/random")
|
||||
admin_url = u("/admin")
|
||||
wtf_url = u("/wtf")
|
||||
|
||||
return f"""<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
@@ -377,7 +350,7 @@ def _build_status_html(*, base_url: str, status_code: int, payload: dict[str, An
|
||||
<div class="chips" aria-label="quick-links">
|
||||
<a class="chip" href="{random_url}"><strong>/random</strong> 随机出图</a>
|
||||
<a class="chip" href="{docs_url}"><strong>/docs</strong> 使用文档</a>
|
||||
<a class="chip" href="{admin_url}"><strong>/admin</strong> 管理后台</a>
|
||||
<a class="chip" href="{wtf_url}"><strong>/wtf</strong> 瀑布流</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -388,7 +361,7 @@ def _build_status_html(*, base_url: str, status_code: int, payload: dict[str, An
|
||||
<div class="k"><div class="label">总图片数</div><div class="val">{images_total}</div><div class="hint">images</div></div>
|
||||
<div class="k"><div class="label">总作品数</div><div class="val">{illust_total}</div><div class="hint">DISTINCT illust_id</div></div>
|
||||
<div class="k"><div class="label">总作者数</div><div class="val">{authors_total}</div><div class="hint">DISTINCT user_id</div></div>
|
||||
<div class="k"><div class="label">更多统计</div><div class="val">—</div><div class="hint"><a href="{admin_url}">后台首页</a></div></div>
|
||||
<div class="k"><div class="label">可用图片数</div><div class="val">{images_enabled}</div><div class="hint">status=1</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -410,7 +383,6 @@ def _build_status_html(*, base_url: str, status_code: int, payload: dict[str, An
|
||||
<select id="metric">
|
||||
<option value="r18">R18 / 非 R18</option>
|
||||
<option value="ai">AI / 非 AI</option>
|
||||
<option value="loli">萝莉 / 非萝莉</option>
|
||||
</select>
|
||||
<span class="muted" id="metricNote"></span>
|
||||
</div>
|
||||
@@ -481,15 +453,6 @@ def _build_status_html(*, base_url: str, status_code: int, payload: dict[str, An
|
||||
]);
|
||||
return;
|
||||
}}
|
||||
if (metric === "loli") {{
|
||||
const l = bd.loli || {{}};
|
||||
note.textContent = (l.tag_found === false) ? "(tags 表中未找到 loli 标签)" : "(标签:loli)";
|
||||
renderPie([
|
||||
{{ label: "萝莉", value: l.loli || 0, color: "#c07046" }},
|
||||
{{ label: "非萝莉", value: l.non_loli || 0, color: "#1f7a56" }},
|
||||
]);
|
||||
return;
|
||||
}}
|
||||
// default: r18
|
||||
note.textContent = "(x_restrict)";
|
||||
const r = bd.r18 || {{}};
|
||||
|
||||
+102
-103
@@ -64,7 +64,7 @@ def _build_wtf_html(*, base_url: str) -> str:
|
||||
a:hover {{ text-decoration: underline; }}
|
||||
|
||||
.wrap {{
|
||||
max-width: 1180px;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 22px 16px 44px;
|
||||
}}
|
||||
@@ -138,29 +138,18 @@ def _build_wtf_html(*, base_url: str) -> str:
|
||||
|
||||
.feed {{
|
||||
margin-top: 14px;
|
||||
column-width: 320px;
|
||||
column-gap: 14px;
|
||||
}}
|
||||
@media (max-width: 520px) {{
|
||||
.feed {{ column-width: auto; column-count: 1; column-gap: 0; }}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}}
|
||||
.item {{
|
||||
break-inside: avoid;
|
||||
margin: 0 0 14px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(40, 24, 16, 0.06);
|
||||
}}
|
||||
.item .meta {{
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
color: rgba(67, 51, 44, 0.72);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 800px;
|
||||
}}
|
||||
.item img {{
|
||||
width: 100%;
|
||||
@@ -174,12 +163,6 @@ def _build_wtf_html(*, base_url: str) -> str:
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
}}
|
||||
.item.fail {{
|
||||
border-color: rgba(180, 35, 24, 0.26);
|
||||
}}
|
||||
.item.fail .meta {{
|
||||
color: rgba(180, 35, 24, 0.86);
|
||||
}}
|
||||
.sentinel {{
|
||||
text-align: center;
|
||||
padding: 18px 0 0;
|
||||
@@ -227,91 +210,103 @@ def _build_wtf_html(*, base_url: str) -> str:
|
||||
info.textContent = baseQuery ? `当前过滤: ?${{baseQuery}}` : "当前过滤: (无)";
|
||||
|
||||
let paused = false;
|
||||
let counter = 0;
|
||||
let seq = 0;
|
||||
let inflight = 0;
|
||||
|
||||
function buildUrl() {{
|
||||
const p = new URLSearchParams(baseParams);
|
||||
p.set("t", String(Date.now()) + "_" + String(counter++));
|
||||
return "/random?" + p.toString();
|
||||
}}
|
||||
|
||||
function addItem() {{
|
||||
const item = document.createElement("div");
|
||||
item.className = "item";
|
||||
const img = document.createElement("img");
|
||||
img.loading = "lazy";
|
||||
img.decoding = "async";
|
||||
img.referrerPolicy = "no-referrer";
|
||||
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "meta";
|
||||
meta.innerHTML = `<span class="muted">#${{String(counter)}}</span><span class="muted">加载中…</span>`;
|
||||
|
||||
let tries = 0;
|
||||
const setSrc = () => {{
|
||||
meta.children[1].textContent = tries > 0 ? `重试 ${{tries}}…` : "加载中…";
|
||||
img.src = buildUrl();
|
||||
}};
|
||||
|
||||
img.onload = () => {{
|
||||
item.classList.add("loaded");
|
||||
meta.children[1].textContent = "OK";
|
||||
inflight = Math.max(0, inflight - 1);
|
||||
updateSentinel();
|
||||
maybePrefill();
|
||||
}};
|
||||
img.onerror = () => {{
|
||||
tries += 1;
|
||||
if (tries <= 3) {{
|
||||
setTimeout(setSrc, 200 * tries);
|
||||
return;
|
||||
}}
|
||||
item.classList.add("fail");
|
||||
meta.children[1].textContent = "加载失败";
|
||||
inflight = Math.max(0, inflight - 1);
|
||||
updateSentinel();
|
||||
maybePrefill();
|
||||
}};
|
||||
|
||||
inflight += 1;
|
||||
updateSentinel();
|
||||
item.appendChild(img);
|
||||
item.appendChild(meta);
|
||||
feed.appendChild(item);
|
||||
setSrc();
|
||||
}}
|
||||
|
||||
function batch(n) {{
|
||||
const count = Math.max(1, Number(n) || 1);
|
||||
for (let i = 0; i < count; i++) addItem();
|
||||
}}
|
||||
let rendered = 0;
|
||||
let target = 0;
|
||||
|
||||
function isMobile() {{
|
||||
return window.matchMedia && window.matchMedia("(max-width: 520px)").matches;
|
||||
}}
|
||||
|
||||
function updateSentinel() {{
|
||||
const items = feed.children.length;
|
||||
const status = paused ? "已暂停" : "加载中";
|
||||
sentinel.textContent = `${{status}} · 已渲染 ${{items}} 张 · in-flight ${{inflight}}`;
|
||||
function cfg() {{
|
||||
const mobile = isMobile();
|
||||
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
||||
const effectiveType = conn && conn.effectiveType ? String(conn.effectiveType) : "";
|
||||
const slow = effectiveType.includes("2g") || effectiveType.includes("3g");
|
||||
|
||||
const base = mobile
|
||||
? { initial: 8, step: 6, maxInflight: 8 }
|
||||
: { initial: 12, step: 10, maxInflight: 14 };
|
||||
|
||||
return {{
|
||||
initial: base.initial,
|
||||
step: base.step,
|
||||
maxInflight: slow ? Math.max(4, Math.floor(base.maxInflight / 2)) : base.maxInflight,
|
||||
}};
|
||||
}}
|
||||
|
||||
function maybePrefill() {{
|
||||
function buildUrl() {{
|
||||
const p = new URLSearchParams(baseParams);
|
||||
p.set("t", String(Date.now()) + "_" + String(seq++));
|
||||
return "/random?" + p.toString();
|
||||
}}
|
||||
|
||||
function updateSentinel() {{
|
||||
const status = paused ? "已暂停" : "加载中";
|
||||
sentinel.textContent = `${{status}} · 已显示 ${{rendered}} 张 · in-flight ${{inflight}}`;
|
||||
}}
|
||||
|
||||
function appendItem(imgEl) {{
|
||||
const item = document.createElement("div");
|
||||
item.className = "item";
|
||||
item.appendChild(imgEl);
|
||||
feed.appendChild(item);
|
||||
requestAnimationFrame(() => item.classList.add("loaded"));
|
||||
}}
|
||||
|
||||
function startOne() {{
|
||||
if (paused) return;
|
||||
const items = feed.children.length;
|
||||
const target = isMobile() ? 10 : 18;
|
||||
if (items < target) {{
|
||||
batch(target - items);
|
||||
const c = cfg();
|
||||
if (inflight >= c.maxInflight) return;
|
||||
if ((rendered + inflight) >= target) return;
|
||||
|
||||
inflight += 1;
|
||||
updateSentinel();
|
||||
|
||||
const img = new Image();
|
||||
img.decoding = "async";
|
||||
img.referrerPolicy = "no-referrer";
|
||||
img.alt = "";
|
||||
|
||||
let tries = 0;
|
||||
const load = () => {{
|
||||
tries += 1;
|
||||
img.src = buildUrl();
|
||||
}};
|
||||
|
||||
img.onload = () => {{
|
||||
inflight = Math.max(0, inflight - 1);
|
||||
rendered += 1;
|
||||
appendItem(img);
|
||||
updateSentinel();
|
||||
ensure();
|
||||
}};
|
||||
img.onerror = () => {{
|
||||
if (tries < 3) {{
|
||||
setTimeout(load, 250 * tries);
|
||||
return;
|
||||
}}
|
||||
inflight = Math.max(0, inflight - 1);
|
||||
updateSentinel();
|
||||
ensure();
|
||||
}};
|
||||
|
||||
load();
|
||||
}}
|
||||
|
||||
function ensure() {{
|
||||
if (paused) return;
|
||||
const c = cfg();
|
||||
while (inflight < c.maxInflight && (rendered + inflight) < target) {{
|
||||
startOne();
|
||||
}}
|
||||
}}
|
||||
|
||||
function loadMore() {{
|
||||
if (paused) return;
|
||||
const maxInflight = isMobile() ? 10 : 18;
|
||||
if (inflight >= maxInflight) return;
|
||||
const n = isMobile() ? 6 : 10;
|
||||
batch(n);
|
||||
function bump() {{
|
||||
const c = cfg();
|
||||
target = Math.max(target, rendered + inflight + c.step);
|
||||
ensure();
|
||||
}}
|
||||
|
||||
toggle.addEventListener("click", () => {{
|
||||
@@ -319,22 +314,27 @@ def _build_wtf_html(*, base_url: str) -> str:
|
||||
toggle.textContent = paused ? "继续加载" : "暂停加载";
|
||||
updateSentinel();
|
||||
if (!paused) {{
|
||||
maybePrefill();
|
||||
loadMore();
|
||||
ensure();
|
||||
}}
|
||||
}});
|
||||
|
||||
const io = new IntersectionObserver((entries) => {{
|
||||
for (const e of entries) {{
|
||||
if (e.isIntersecting) loadMore();
|
||||
if (e.isIntersecting) bump();
|
||||
}}
|
||||
}}, {{ root: null, rootMargin: "900px 0px", threshold: 0 }});
|
||||
}}, {{ root: null, rootMargin: "2200px 0px", threshold: 0 }});
|
||||
io.observe(sentinel);
|
||||
|
||||
// Initial fill.
|
||||
maybePrefill();
|
||||
loadMore();
|
||||
target = cfg().initial;
|
||||
ensure();
|
||||
updateSentinel();
|
||||
|
||||
window.addEventListener("resize", () => {{
|
||||
const c = cfg();
|
||||
target = Math.max(target, c.initial);
|
||||
ensure();
|
||||
}});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -349,4 +349,3 @@ async def wtf_page(request: Request) -> HTMLResponse:
|
||||
resp = HTMLResponse(content=html, status_code=200, headers={"Cache-Control": "no-store"})
|
||||
set_request_id_header(resp, rid)
|
||||
return resp
|
||||
|
||||
Reference in new issue
Block a user