提交
This commit is contained in:
278
public/css/app.css
Normal file
278
public/css/app.css
Normal file
@@ -0,0 +1,278 @@
|
||||
:root {
|
||||
--page-max: 920px;
|
||||
--page-pad-x: 24px;
|
||||
--page-pad-y: 24px;
|
||||
--page-gap: 24px;
|
||||
--text: #111;
|
||||
--muted: #555;
|
||||
--line: #e8e8e8;
|
||||
}
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
font-family: "FangSong", serif;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
font-family: "FangSong", serif;
|
||||
line-height: 1.7;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
main {
|
||||
width: min(100%, var(--page-max));
|
||||
margin: 0 auto;
|
||||
padding: var(--page-pad-y) var(--page-pad-x) 32px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.site-footer {
|
||||
width: min(100%, var(--page-max));
|
||||
margin: 0 auto;
|
||||
padding: 20px var(--page-pad-x) 28px;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid var(--line);
|
||||
text-align: center;
|
||||
font-size: 0.88rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.site-footer p {
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
header.site-header {
|
||||
margin-bottom: var(--page-gap);
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.site-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.site-titles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.site-title-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.site-title-en {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.site-logo {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
}
|
||||
.admin-gate {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.admin-gate[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
.admin-gate-panel {
|
||||
background: #fff;
|
||||
padding: 16px 18px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
width: min(100%, 420px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.admin-gate-panel input[type="password"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
line-height: 1.3;
|
||||
margin: 0 0 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
article,
|
||||
section {
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
input,
|
||||
textarea,
|
||||
button {
|
||||
font: inherit;
|
||||
max-width: 100%;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
textarea {
|
||||
min-height: 320px;
|
||||
resize: vertical;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow-x: auto;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
hr {
|
||||
border: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 24px 0;
|
||||
}
|
||||
.post-card {
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #efefef;
|
||||
}
|
||||
.post-card:last-of-type {
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.post-card .post-excerpt {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.post-card .post-meta {
|
||||
font-size: 0.9em;
|
||||
color: #555;
|
||||
margin: 0 0 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 16px;
|
||||
}
|
||||
.post-detail-meta {
|
||||
font-size: 0.95em;
|
||||
color: #555;
|
||||
margin: 0 0 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 16px;
|
||||
}
|
||||
.post-detail-views {
|
||||
color: #666;
|
||||
}
|
||||
.post-comments {
|
||||
margin-top: 36px;
|
||||
padding-top: 28px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.post-comments__title {
|
||||
font-size: 1.35rem;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
:root {
|
||||
--page-max: 840px;
|
||||
--page-pad-x: 20px;
|
||||
--page-pad-y: 20px;
|
||||
--page-gap: 20px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
:root {
|
||||
--page-max: 100%;
|
||||
--page-pad-x: 16px;
|
||||
--page-pad-y: 16px;
|
||||
--page-gap: 18px;
|
||||
}
|
||||
.site-logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.site-title-link {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.post-card .post-meta,
|
||||
.post-detail-meta {
|
||||
gap: 6px 12px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
:root {
|
||||
--page-pad-x: 14px;
|
||||
--page-pad-y: 14px;
|
||||
--page-gap: 16px;
|
||||
}
|
||||
body {
|
||||
line-height: 1.68;
|
||||
}
|
||||
header.site-header {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
textarea {
|
||||
min-height: 240px;
|
||||
}
|
||||
.site-brand {
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.site-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.site-title-link {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.post-card .post-meta,
|
||||
.post-detail-meta {
|
||||
font-size: 0.82em;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 4px 10px;
|
||||
row-gap: 4px;
|
||||
}
|
||||
.post-card .post-meta span,
|
||||
.post-detail-meta span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.admin-gate-panel {
|
||||
padding: 14px 14px;
|
||||
}
|
||||
}
|
||||
59
public/js/app.js
Normal file
59
public/js/app.js
Normal file
@@ -0,0 +1,59 @@
|
||||
(function () {
|
||||
var logo = document.querySelector(".site-logo");
|
||||
var gate = document.getElementById("admin-gate");
|
||||
var inp = document.getElementById("admin-token");
|
||||
var btnGo = document.getElementById("admin-token-go");
|
||||
var btnCancel = document.getElementById("admin-token-cancel");
|
||||
if (!logo || !gate || !inp || !btnGo || !btnCancel) return;
|
||||
var n = 0;
|
||||
var resetTimer = null;
|
||||
function showGate() {
|
||||
gate.hidden = false;
|
||||
inp.value = "";
|
||||
inp.focus();
|
||||
}
|
||||
function hideGate() {
|
||||
gate.hidden = true;
|
||||
}
|
||||
logo.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
if (resetTimer) clearTimeout(resetTimer);
|
||||
n += 1;
|
||||
if (n >= 5) {
|
||||
n = 0;
|
||||
showGate();
|
||||
return;
|
||||
}
|
||||
resetTimer = setTimeout(function () {
|
||||
n = 0;
|
||||
}, 4000);
|
||||
});
|
||||
btnCancel.addEventListener("click", hideGate);
|
||||
function submit() {
|
||||
fetch("/admin/session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ token: inp.value })
|
||||
})
|
||||
.then(function (r) {
|
||||
if (r.ok) {
|
||||
hideGate();
|
||||
location.href = "/admin";
|
||||
return;
|
||||
}
|
||||
inp.select();
|
||||
alert("Token 无效");
|
||||
})
|
||||
.catch(function () {
|
||||
alert("网络错误");
|
||||
});
|
||||
}
|
||||
btnGo.addEventListener("click", submit);
|
||||
inp.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}
|
||||
});
|
||||
})();
|
||||
78
public/js/cwd-comments.js
Normal file
78
public/js/cwd-comments.js
Normal file
@@ -0,0 +1,78 @@
|
||||
(function () {
|
||||
/**
|
||||
* cwd-widget(≤0.1.11)里 getLikeStatus 用 post_slug = 完整页面 URL(postUrl),
|
||||
* 而点赞写入 likePage 用的是 postSlug;后端排行榜按 slug 汇总,导致前台一直显示 0。
|
||||
* getPagePv 已写成 postUrl || postSlug,点赞查询未对齐。这里把 GET /api/like 的
|
||||
* post_slug 从绝对地址改回与评论区一致的 slug。
|
||||
*/
|
||||
function patchCwdLikeGetQuery(apiBaseNorm, postSlug) {
|
||||
if (window.__cwdLikeStatusQueryPatched) return;
|
||||
window.__cwdLikeStatusQueryPatched = true;
|
||||
|
||||
var base = apiBaseNorm;
|
||||
var orig = window.fetch;
|
||||
window.fetch = function (input, init) {
|
||||
var url =
|
||||
typeof input === "string"
|
||||
? input
|
||||
: input && typeof input.url === "string"
|
||||
? input.url
|
||||
: "";
|
||||
var method = "GET";
|
||||
if (init && init.method) method = String(init.method).toUpperCase();
|
||||
else if (typeof Request !== "undefined" && input instanceof Request) {
|
||||
method = String(input.method || "GET").toUpperCase();
|
||||
}
|
||||
if (
|
||||
url &&
|
||||
url.indexOf(base) === 0 &&
|
||||
url.indexOf("/api/like?") !== -1 &&
|
||||
method === "GET"
|
||||
) {
|
||||
try {
|
||||
var u = new URL(url);
|
||||
var ps = u.searchParams.get("post_slug") || "";
|
||||
if (ps.indexOf("://") !== -1) {
|
||||
u.searchParams.set("post_slug", postSlug);
|
||||
if (typeof input === "string") {
|
||||
input = u.toString();
|
||||
} else if (typeof Request !== "undefined" && input instanceof Request) {
|
||||
input = new Request(u.toString(), input);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return orig.call(this, input, init);
|
||||
};
|
||||
}
|
||||
|
||||
function mount() {
|
||||
var el = document.getElementById("comments");
|
||||
if (!el || typeof CWDComments === "undefined") return;
|
||||
|
||||
var ds = el.dataset;
|
||||
var apiBaseUrl = ds.apiBase;
|
||||
var postSlug = ds.postSlug;
|
||||
if (!apiBaseUrl || !postSlug) return;
|
||||
|
||||
patchCwdLikeGetQuery(apiBaseUrl.replace(/\/$/, ""), postSlug);
|
||||
|
||||
var opts = {
|
||||
el: "#comments",
|
||||
apiBaseUrl: apiBaseUrl,
|
||||
postSlug: postSlug,
|
||||
lang: ds.lang || "zh-CN"
|
||||
};
|
||||
if (ds.siteId) opts.siteId = ds.siteId;
|
||||
|
||||
new CWDComments(opts).mount();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", mount);
|
||||
} else {
|
||||
mount();
|
||||
}
|
||||
})();
|
||||
@@ -9,7 +9,8 @@ marked.use(
|
||||
nonStandard: true
|
||||
})
|
||||
);
|
||||
marked.setOptions({ gfm: true, breaks: false });
|
||||
// breaks: true — 段内单换行渲染为 <br>,与 Obsidian 等编辑器的换行习惯一致(CommonMark 默认会把单换行当成空格)
|
||||
marked.setOptions({ gfm: true, breaks: true });
|
||||
|
||||
function decodeHtmlEntities(str) {
|
||||
return String(str)
|
||||
|
||||
Reference in New Issue
Block a user