132 lines
4.7 KiB
JavaScript
132 lines
4.7 KiB
JavaScript
import {
|
|
escapeHtml,
|
|
escapeAttr,
|
|
formatDateTime,
|
|
cleanText,
|
|
stripMarkdown,
|
|
b64EncodeUtf8
|
|
} from "./utils.js";
|
|
|
|
const DEFAULT_SITE_NAME_ZH = "萌芽小窝";
|
|
const DEFAULT_SITE_NAME_EN = "SproutBlog";
|
|
|
|
export function siteNameZh(env) {
|
|
return cleanText(env?.SITE_TITLE) || DEFAULT_SITE_NAME_ZH;
|
|
}
|
|
|
|
export function siteNameEn(env) {
|
|
return cleanText(env?.SITE_TITLE_EN) || DEFAULT_SITE_NAME_EN;
|
|
}
|
|
|
|
export function pageTitle(env, suffix = "") {
|
|
const site = siteNameZh(env);
|
|
return suffix ? `${site} - ${suffix}` : site;
|
|
}
|
|
|
|
export function renderLayout(title, body, env, options = {}) {
|
|
const siteZh = siteNameZh(env);
|
|
const siteEn = siteNameEn(env);
|
|
const cwdPostSlug = options.cwdPostSlug ? String(options.cwdPostSlug) : "";
|
|
const cwdApiBase =
|
|
cleanText(env?.CWD_API_BASE) || "https://cwd.api.smyhub.com";
|
|
const cwdSiteId = cleanText(env?.CWD_SITE_ID);
|
|
const cwdSection = cwdPostSlug
|
|
? `
|
|
<section class="post-comments" aria-labelledby="comments-heading">
|
|
<h2 id="comments-heading" class="post-comments__title">评论</h2>
|
|
<div id="comments" data-api-base="${escapeAttr(cwdApiBase)}" data-post-slug="${escapeAttr(cwdPostSlug)}" data-lang="zh-CN"${cwdSiteId ? ` data-site-id="${escapeAttr(cwdSiteId)}"` : ""}></div>
|
|
</section>`
|
|
: "";
|
|
const mdHead = options.markdownPage
|
|
? `
|
|
<link rel="stylesheet" href="/css/markdown.css">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css" crossorigin="anonymous">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github.min.css" crossorigin="anonymous">
|
|
`
|
|
: "";
|
|
const mdScript = options.markdownPage
|
|
? `\n <script type="module" src="/js/markdown-client.js"></script>`
|
|
: "";
|
|
const cwdScripts = cwdPostSlug
|
|
? `
|
|
<script src="https://cdn.jsdelivr.net/npm/cwd-widget@0.1.11/dist/cwd.umd.js" defer></script>
|
|
<script src="/js/cwd-comments.js" defer></script>`
|
|
: "";
|
|
return `<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
|
<title>${escapeHtml(title)}</title>
|
|
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
<link rel="stylesheet" href="/css/app.css">
|
|
${mdHead}
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<header class="site-header">
|
|
<div class="site-brand">
|
|
<img class="site-logo" src="/logo.png" alt="" width="40" height="40" decoding="async">
|
|
<div class="site-titles">
|
|
<a href="/" class="site-title-link">${escapeHtml(siteZh)}</a>
|
|
<span class="site-title-en">${escapeHtml(siteEn)}</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
${body}
|
|
${cwdSection}
|
|
</main>
|
|
<footer class="site-footer">
|
|
<p>萌芽小窝-一个安静的小地方 @2026-至今</p>
|
|
</footer>
|
|
<div id="admin-gate" class="admin-gate" hidden>
|
|
<div class="admin-gate-panel">
|
|
<input id="admin-token" type="password" autocomplete="off" placeholder="Token" aria-label="Token">
|
|
<button type="button" id="admin-token-go">进入</button>
|
|
<button type="button" id="admin-token-cancel">取消</button>
|
|
</div>
|
|
</div>
|
|
<script src="/js/app.js" defer></script>${mdScript}${cwdScripts}
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
export function renderPostCard(post) {
|
|
const excerpt = post.excerpt || stripMarkdown(post.content).slice(0, 180);
|
|
const views = Number(post.view_count) || 0;
|
|
return `
|
|
<article class="post-card">
|
|
<h2><a href="/post/${encodeURIComponent(post.slug)}">${escapeHtml(post.title)}</a></h2>
|
|
<p class="post-excerpt">${escapeHtml(excerpt)}${excerpt.length >= 180 ? "…" : ""}</p>
|
|
<p class="post-meta">
|
|
<span class="post-card-time">创建于:${formatDateTime(post.created_at)}</span>
|
|
<span class="post-card-time">最后更新于:${formatDateTime(post.updated_at)}</span>
|
|
<span class="post-card-views">浏览 ${views}</span>
|
|
</p>
|
|
</article>
|
|
`;
|
|
}
|
|
|
|
export function renderAdminCard(post) {
|
|
return `
|
|
<article>
|
|
<h3>${escapeHtml(post.title)}</h3>
|
|
<p>ID: ${post.id} | Slug: ${escapeHtml(post.slug)} | ${post.published ? "已发布" : "草稿"} | ${formatDateTime(post.updated_at)}</p>
|
|
<div>
|
|
<a href="/admin/edit/${post.id}">编辑</a>
|
|
<form method="post" action="/admin/delete" style="display:inline">
|
|
<input type="hidden" name="id" value="${post.id}">
|
|
<button type="submit">删除</button>
|
|
</form>
|
|
</div>
|
|
</article>
|
|
`;
|
|
}
|
|
|
|
export function markdownPlaceholder(raw) {
|
|
const src = String(raw ?? "");
|
|
if (!src.trim()) return "";
|
|
const b64 = b64EncodeUtf8(src);
|
|
return `<div class="markdown-body markdown-pending" data-b64-md="${escapeAttr(b64)}"><p class="markdown-loading">加载中…</p></div>`;
|
|
}
|