chore: 集成 extra-api 统计、页脚备案配置、友链申请内嵌
- extra-api: 文章卡片/详情页展示字数+阅读时长、归档页展示全站总字数、瞬间页展示单条字数(pluginFinder.available 守卫,未安装自动隐藏) - 页脚新增 ICP 备案号与公网安备号配置(settings.yaml footer 分组,partials/layout.html 渲染并加安全导航守卫) - 友链申请表单由独立单页改为内嵌 /links 页底部(点击「申请友链」展开),移除独立 links-apply.html 及其自定义模板注册 - 重新打包 dist/whitespace-0.1.0.zip(移除陈旧 1.0.0)
This commit is contained in:
+204
@@ -56,6 +56,71 @@
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.links-apply {
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.link-apply-panel {
|
||||
max-width: 32rem;
|
||||
margin: 1.25rem auto 0;
|
||||
text-align: left;
|
||||
}
|
||||
.link-apply-title {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
.apply-form .form-row {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.apply-form label {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.apply-form input,
|
||||
.apply-form textarea {
|
||||
width: 100%;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
}
|
||||
.apply-form textarea {
|
||||
min-height: 5rem;
|
||||
resize: vertical;
|
||||
}
|
||||
.apply-captcha {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
.apply-captcha img {
|
||||
height: 2.75rem;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
cursor: pointer;
|
||||
}
|
||||
.apply-status {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: var(--radius-sm);
|
||||
display: none;
|
||||
}
|
||||
.apply-status.ok {
|
||||
display: block;
|
||||
background: rgba(46, 160, 67, 0.12);
|
||||
color: #2ea043;
|
||||
border: 1px solid rgba(46, 160, 67, 0.4);
|
||||
}
|
||||
.apply-status.err {
|
||||
display: block;
|
||||
background: rgba(229, 57, 53, 0.12);
|
||||
color: #e53935;
|
||||
border: 1px solid rgba(229, 57, 53, 0.4);
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
@@ -96,10 +161,149 @@
|
||||
</li>
|
||||
</ul>
|
||||
<p th:unless="${not #lists.isEmpty(links)}">暂无链接</p>
|
||||
|
||||
<div class="links-apply">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
id="link-apply-toggle"
|
||||
type="button"
|
||||
th:text="#{links.apply}"
|
||||
>申请友链</button>
|
||||
<div class="link-apply-panel apply-form" id="link-apply-panel" hidden>
|
||||
<h2 class="link-apply-title" th:text="#{links.apply}">申请友链</h2>
|
||||
<form id="link-apply-form" novalidate>
|
||||
<div class="form-row">
|
||||
<label for="f-url">网站地址 *</label>
|
||||
<input id="f-url" name="url" type="url" required placeholder="https://example.com" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="f-name">网站名称 *</label>
|
||||
<input id="f-name" name="displayName" type="text" required placeholder="我的站点" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="f-logo">Logo 地址</label>
|
||||
<input id="f-logo" name="logo" type="url" placeholder="https://example.com/logo.png" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="f-desc">网站描述</label>
|
||||
<input id="f-desc" name="description" type="text" placeholder="一句话介绍你的站点" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="f-email">联系邮箱</label>
|
||||
<input id="f-email" name="email" type="email" placeholder="you@example.com" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="f-backlink">反链页面</label>
|
||||
<input id="f-backlink" name="backlink" type="url" placeholder="含本站链接的页面地址" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="f-feeds">RSS / Atom 地址(每行一个)</label>
|
||||
<textarea id="f-feeds" name="feedUrls" placeholder="https://example.com/feed.xml"></textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="f-captcha">验证码 *</label>
|
||||
<div class="apply-captcha">
|
||||
<img id="captcha-img" alt="验证码" title="点击刷新" />
|
||||
<input id="f-captcha" name="captchaCode" type="text" maxlength="5" required autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">提交申请</button>
|
||||
</form>
|
||||
<div class="apply-status" id="apply-status" role="alert"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="comments-section" th:if="${haloCommentEnabled}">
|
||||
<halo:comment group="plugin.halo.run" kind="Plugin" th:attr="name=${pluginName}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:inline="none">
|
||||
(function () {
|
||||
var API = "/apis/api.link.halo.run/v1alpha1/link-applications";
|
||||
var CAPTCHA = "/apis/api.link.halo.run/v1alpha1/link-applications/captcha";
|
||||
var toggle = document.getElementById("link-apply-toggle");
|
||||
var panel = document.getElementById("link-apply-panel");
|
||||
var form = document.getElementById("link-apply-form");
|
||||
var img = document.getElementById("captcha-img");
|
||||
var status = document.getElementById("apply-status");
|
||||
var challengeId = null;
|
||||
|
||||
function loadCaptcha() {
|
||||
fetch(CAPTCHA, { method: "POST" })
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error("captcha");
|
||||
return r.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
challengeId = data.challengeId;
|
||||
img.src = data.image;
|
||||
})
|
||||
.catch(function () {
|
||||
status.className = "apply-status err";
|
||||
status.textContent = "验证码加载失败,请刷新页面重试。";
|
||||
});
|
||||
}
|
||||
|
||||
toggle.addEventListener("click", function () {
|
||||
panel.hidden = !panel.hidden;
|
||||
if (!panel.hidden && !challengeId) {
|
||||
loadCaptcha();
|
||||
}
|
||||
});
|
||||
|
||||
img.addEventListener("click", loadCaptcha);
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
status.className = "apply-status";
|
||||
status.textContent = "";
|
||||
if (!challengeId) {
|
||||
status.className = "apply-status err";
|
||||
status.textContent = "请先点击「申请友链」并等待验证码加载。";
|
||||
return;
|
||||
}
|
||||
var fd = new FormData(form);
|
||||
var feeds = (fd.get("feedUrls") || "")
|
||||
.split(/\r?\n/)
|
||||
.map(function (s) { return s.trim(); })
|
||||
.filter(Boolean);
|
||||
var payload = {
|
||||
url: fd.get("url"),
|
||||
displayName: fd.get("displayName"),
|
||||
logo: fd.get("logo") || undefined,
|
||||
description: fd.get("description") || undefined,
|
||||
email: fd.get("email") || undefined,
|
||||
backlink: fd.get("backlink") || undefined,
|
||||
feedUrls: feeds,
|
||||
challengeId: challengeId,
|
||||
captchaCode: fd.get("captchaCode")
|
||||
};
|
||||
fetch(API, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(function (r) {
|
||||
if (r.status === 201) {
|
||||
form.reset();
|
||||
challengeId = null;
|
||||
loadCaptcha();
|
||||
status.className = "apply-status ok";
|
||||
status.textContent = "申请已提交,等待管理员审核。";
|
||||
} else {
|
||||
return r.json().then(function (err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
loadCaptcha();
|
||||
status.className = "apply-status err";
|
||||
status.textContent = "提交失败,请检查信息后重试(验证码已刷新)。";
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</include>
|
||||
|
||||
Reference in New Issue
Block a user