Files
sproutblog/render.js
2026-04-15 13:20:51 +08:00

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>`;
}