feat(site): 页头加上 GitHub 仓库与 Telegram 频道链接
做成配置项 LDR_HEADER_LINKS,格式 "名称|地址" 逗号分隔, 不硬编码 —— 以后换地址改环境变量即可,不用动代码。 默认值: GitHub https://github.com/lowseekai/let-deals-radar Telegram 频道 https://t.me/LowSeekNews 细节 - 解析时跳过非法项(缺分隔符、地址不是 http 开头) - 未配置则整段不输出,不留空占位 - 外链带 target=_blank + rel=noopener 验证:235 测试通过(新增 4 个)。
This commit is contained in:
1 parent
c70361796c
commit
1f0554acb7
3 files changed
+83
No files matched your search
@@ -63,6 +63,32 @@ SITE_DESC = _env(
|
||||
SITE_URL = _env("LDR_SITE_URL", "").rstrip("/")
|
||||
SITE_FOOTER = _env("LDR_SITE_FOOTER", "")
|
||||
|
||||
# 页头的附加链接,格式 "名称|地址",多项用逗号分隔。
|
||||
# 例:LDR_HEADER_LINKS="GitHub|https://github.com/x/y, Telegram|https://t.me/z"
|
||||
# 留空则不显示 —— 不影响现有布局。
|
||||
HEADER_LINKS_RAW = _env(
|
||||
"LDR_HEADER_LINKS",
|
||||
"GitHub|https://github.com/lowseekai/let-deals-radar,"
|
||||
" Telegram 频道|https://t.me/LowSeekNews",
|
||||
)
|
||||
|
||||
|
||||
def _parse_header_links(raw: str) -> list[tuple[str, str]]:
|
||||
"""把 "名称|地址,..." 解析成 [(名称, 地址)],非法项跳过。"""
|
||||
out: list[tuple[str, str]] = []
|
||||
for chunk in (raw or "").split(","):
|
||||
chunk = chunk.strip()
|
||||
if not chunk or "|" not in chunk:
|
||||
continue
|
||||
name, url = chunk.split("|", 1)
|
||||
name, url = name.strip(), url.strip()
|
||||
if name and url.startswith(("http://", "https://")):
|
||||
out.append((name, url))
|
||||
return out
|
||||
|
||||
|
||||
HEADER_LINKS = _parse_header_links(HEADER_LINKS_RAW)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 数据源
|
||||
|
||||
@@ -153,6 +153,19 @@ def _json_for_script(obj) -> str:
|
||||
.replace("&", "\\u0026"))
|
||||
|
||||
|
||||
def _header_links_html() -> str:
|
||||
"""渲染页头附加链接。未配置时返回空串(不影响布局)。"""
|
||||
links = getattr(config, "HEADER_LINKS", None) or []
|
||||
if not links:
|
||||
return ""
|
||||
parts = []
|
||||
for name, url in links:
|
||||
parts.append('<span><a href="%s" target="_blank" rel="noopener">%s</a>'
|
||||
'</span>' % (html.escape(url, quote=True),
|
||||
html.escape(name)))
|
||||
return "\n ".join(parts)
|
||||
|
||||
|
||||
SITE_TEMPLATE = r"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
@@ -301,6 +314,7 @@ footer{margin-top:28px;color:var(--muted);font-size:12px;line-height:1.9}
|
||||
<span>共 <b id="m-total">@@COUNT@@</b> 条</span>
|
||||
<span>生成于 <b>@@GENERATED@@</b></span>
|
||||
<span><a href="data/deals.json">原始 JSON</a></span>
|
||||
@@HEADER_LINKS@@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -668,6 +682,7 @@ def build_site(payload: dict, out_dir=None, verbose: bool = True) -> str:
|
||||
.replace("@@EVENT_CN@@", json.dumps(EVENT_CN, ensure_ascii=False))
|
||||
.replace("@@AVAIL_CN@@", json.dumps(AVAIL_CN, ensure_ascii=False))
|
||||
.replace("@@FXNOTE@@", html.escape(config.FX_NOTE))
|
||||
.replace("@@HEADER_LINKS@@", _header_links_html())
|
||||
.replace("@@FOOTER@@",
|
||||
html.escape(config.SITE_FOOTER) if config.SITE_FOOTER
|
||||
else ""))
|
||||
|
||||
@@ -289,6 +289,48 @@ def test_forum_links_corrupt_file_is_safe(tmp_path):
|
||||
mp.undo()
|
||||
|
||||
|
||||
def test_header_links_rendered(tmp_path):
|
||||
"""页头应渲染配置的附加链接(GitHub / Telegram)。"""
|
||||
render.build_site(_sample_payload(), str(tmp_path), verbose=False)
|
||||
html = open(os.path.join(tmp_path, "index.html"), encoding="utf-8").read()
|
||||
|
||||
links = render.config.HEADER_LINKS
|
||||
assert links, "默认应配置页头链接"
|
||||
for name, url in links:
|
||||
assert name in html, "缺少链接名:%s" % name
|
||||
assert url in html, "缺少链接地址:%s" % url
|
||||
assert 'rel="noopener"' in html
|
||||
|
||||
|
||||
def test_header_links_empty_is_safe(tmp_path):
|
||||
"""未配置页头链接时不报错、不留占位符。"""
|
||||
import pytest as _pytest
|
||||
mp = _pytest.MonkeyPatch()
|
||||
mp.setattr(render.config, "HEADER_LINKS", [])
|
||||
try:
|
||||
render.build_site(_sample_payload(), str(tmp_path), verbose=False)
|
||||
finally:
|
||||
mp.undo()
|
||||
html = open(os.path.join(tmp_path, "index.html"), encoding="utf-8").read()
|
||||
assert "@@HEADER_LINKS@@" not in html
|
||||
|
||||
|
||||
def test_parse_header_links():
|
||||
"""解析 "名称|地址,...",非法项跳过。"""
|
||||
from letdeals.config import _parse_header_links
|
||||
got = _parse_header_links(
|
||||
"GitHub|https://github.com/x, Bad|not-a-url, ,"
|
||||
" TG|https://t.me/y")
|
||||
assert got == [("GitHub", "https://github.com/x"),
|
||||
("TG", "https://t.me/y")]
|
||||
|
||||
|
||||
def test_parse_header_links_empty():
|
||||
from letdeals.config import _parse_header_links
|
||||
assert _parse_header_links("") == []
|
||||
assert _parse_header_links("没有分隔符") == []
|
||||
|
||||
|
||||
def test_link_rendering_both_cases(tmp_path):
|
||||
"""链接区两种形态:有论坛帖=主+次两个;无=只有原帖。"""
|
||||
render.build_site(_sample_payload(), str(tmp_path), verbose=False)
|
||||
|
||||
Reference in new issue
Block a user