B7 review-response batch: derived about counter, CJK-aware reading time, ZH copy fixes, hero leads with capability
Deploy / build (push) Successful in 26s
Deploy / build (push) Successful in 26s
This commit is contained in:
1 parent
0b23312948
commit
5941e858cc
10 files changed
+87
-27
No files matched your search
@@ -1,7 +1,13 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import AuthorCard from '../components/AuthorCard.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isEn } from '../lib/lang';
|
||||
import { SITE } from '../config';
|
||||
|
||||
// Counted from the content collection, never typed by hand — a hardcoded number
|
||||
// here already drifted once (71 while the homepage rendered 72).
|
||||
const writeUps = (await getCollection('posts', isEn)).length;
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
@@ -47,8 +53,8 @@ import { SITE } from '../config';
|
||||
<div class="proof-grid">
|
||||
<div class="proof-card"><span class="n">162</span><span class="l">containers in production</span></div>
|
||||
<div class="proof-card"><span class="n">3</span><span class="l">self-managed hosts (78 compose stacks)</span></div>
|
||||
<div class="proof-card"><span class="n">71</span><span class="l">write-ups, each in English and 中文</span></div>
|
||||
<div class="proof-card"><span class="n">0</span><span class="l">hosting companies between me and the servers</span></div>
|
||||
<div class="proof-card"><span class="n">{writeUps}</span><span class="l">write-ups, each in English and 中文</span></div>
|
||||
<div class="proof-card"><span class="n">0</span><span class="l">managed hosting layers — I run all three hosts myself</span></div>
|
||||
</div>
|
||||
<p>
|
||||
That footprint is client websites, a mail platform, CI/CD, workflow automation,
|
||||
|
||||
@@ -47,9 +47,10 @@ const featuredMetrics: Record<string, string> = {
|
||||
<h1>{SITE.author} — full-stack developer & DevOps engineer</h1>
|
||||
</div>
|
||||
<p class="hero-sub">
|
||||
I run <strong>162 containers across three self-managed hosts</strong> — client sites, a
|
||||
mail platform, CI/CD, workflow automation and this blog — and I build web systems for
|
||||
Malaysian SMEs. I write up what broke, what the evidence said, and how I fixed it.
|
||||
I build and operate production web systems — from the code down to the servers.
|
||||
<strong>162 containers across three self-managed hosts</strong> run client sites, a mail
|
||||
platform, CI/CD, workflow automation and this blog. I write up what broke, what the
|
||||
evidence said, and how I fixed it.
|
||||
</p>
|
||||
<p class="hero-cta">
|
||||
<a class="btn btn-primary" href="#featured">Read the engineering case studies ↓</a>
|
||||
@@ -72,6 +73,8 @@ const featuredMetrics: Record<string, string> = {
|
||||
<div class="bento-visual" aria-hidden="true">
|
||||
<img
|
||||
src={`/og/thumb/${cleanSlug}-800.webp`}
|
||||
srcset={`/og/thumb/${cleanSlug}-400.webp 400w, /og/thumb/${cleanSlug}-800.webp 800w`}
|
||||
sizes="(max-width: 640px) 60vw, 341px"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
|
||||
@@ -3,6 +3,7 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import AuthorCard from '../../components/AuthorCard.astro';
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import { postLang } from '../../lib/lang';
|
||||
import { countWords, readingMinutes } from '../../lib/reading-time';
|
||||
import { tagCounts, tagLink } from '../../lib/tags';
|
||||
import { SITE } from '../../config';
|
||||
|
||||
@@ -19,15 +20,14 @@ const { Content } = await render(post);
|
||||
|
||||
const lang = postLang(post.slug);
|
||||
|
||||
// Reading time (~200 wpm)
|
||||
const words = post.body?.trim().split(/\s+/).length ?? 0;
|
||||
const minutes = Math.max(1, Math.round(words / 200));
|
||||
// Reading time — CJK-aware (src/lib/reading-time.ts). A plain whitespace split
|
||||
// made every Chinese post read "1 min" while its English twin read five.
|
||||
const words = countWords(post.body);
|
||||
const minutes = readingMinutes(post.body);
|
||||
|
||||
const fmt = (d: Date) =>
|
||||
d.toLocaleDateString(lang === 'zh' ? 'zh-CN' : 'en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
|
||||
const minutesOf = (body?: string) => Math.max(1, Math.round((body?.trim().split(/\s+/).length ?? 0) / 200));
|
||||
|
||||
// Related posts: same language, same category or overlapping tags, excluding self
|
||||
const all = await getCollection('posts', ({ data }) => !data.draft);
|
||||
// Tag pills: a tag with its own page becomes a link, a one-off tag stays plain text.
|
||||
@@ -97,7 +97,7 @@ const jsonLd = {
|
||||
<div class="meta">
|
||||
<time datetime={post.data.pubDate.toISOString()}>{fmt(post.data.pubDate)}</time>
|
||||
<span class="sep">·</span>
|
||||
<span>{minutes} min read</span>
|
||||
<span>{minutes} {lang === 'zh' ? '分钟' : 'min read'}</span>
|
||||
<span class="sep">·</span>
|
||||
<a class="tag" href={lang === 'zh' ? `/zh/categories/${post.data.category}/` : `/categories/${post.data.category}/`}>{post.data.category}</a>
|
||||
{post.data.tags.map((t) => {
|
||||
@@ -135,7 +135,7 @@ const jsonLd = {
|
||||
<span class="related-badge">{p.data.category}</span>
|
||||
<h3>{p.data.title}</h3>
|
||||
<time datetime={p.data.pubDate.toISOString()}>
|
||||
{fmt(p.data.pubDate)} · {minutesOf(p.body)} {lang === 'zh' ? '分钟' : 'min read'}
|
||||
{fmt(p.data.pubDate)} · {readingMinutes(p.body)} {lang === 'zh' ? '分钟' : 'min read'}
|
||||
</time>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import AuthorCard from '../../components/AuthorCard.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isZh } from '../../lib/lang';
|
||||
import { SITE } from '../../config';
|
||||
|
||||
// Counted from the content collection, never typed by hand — a hardcoded number
|
||||
// here already drifted once (71 while the homepage rendered 72).
|
||||
const writeUps = (await getCollection('posts', isZh)).length;
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
@@ -46,8 +52,8 @@ import { SITE } from '../../config';
|
||||
<div class="proof-grid">
|
||||
<div class="proof-card"><span class="n">162</span><span class="l">个生产环境容器</span></div>
|
||||
<div class="proof-card"><span class="n">3</span><span class="l">台自管服务器(78 个 compose 栈)</span></div>
|
||||
<div class="proof-card"><span class="n">71</span><span class="l">篇长文,每篇中英双版</span></div>
|
||||
<div class="proof-card"><span class="n">0</span><span class="l">夹在我和服务器之间的托管商</span></div>
|
||||
<div class="proof-card"><span class="n">{writeUps}</span><span class="l">篇长文,每篇中英双版</span></div>
|
||||
<div class="proof-card"><span class="n">0</span><span class="l">层托管服务——三台主机都我自己运维</span></div>
|
||||
</div>
|
||||
<p>
|
||||
这套环境里有客户网站、邮件平台、CI/CD、流程自动化、统计和这个博客,全靠手工拴在一起。
|
||||
|
||||
@@ -51,9 +51,7 @@ const featuredMetrics: Record<string, string> = {
|
||||
<h1>{SITE.author} —— 全栈开发者兼 DevOps 工程师</h1>
|
||||
</div>
|
||||
<p class="hero-sub">
|
||||
我独自运维 <strong>三台自管服务器上的 162 个容器</strong>(客户站点、邮件平台、
|
||||
CI/CD、流程自动化与这个博客),并为马来西亚中小企业构建 Web 系统。这里写的是
|
||||
「坏了什么、证据在哪、怎么修好」。
|
||||
从代码到服务器,生产环境的 Web 系统我自己从头做到尾。<strong>三台自管服务器上跑着 162 个容器</strong>:客户站点、邮件平台、CI/CD、流程自动化,还有这个博客。这里写的是「坏了什么、证据在哪、怎么修好」。
|
||||
</p>
|
||||
<p class="hero-cta">
|
||||
<a class="btn btn-primary" href="#featured">先看工程案例 ↓</a>
|
||||
@@ -76,6 +74,8 @@ const featuredMetrics: Record<string, string> = {
|
||||
<div class="bento-visual" aria-hidden="true">
|
||||
<img
|
||||
src={`/og/thumb/${cleanSlug}-800.webp`}
|
||||
srcset={`/og/thumb/${cleanSlug}-400.webp 400w, /og/thumb/${cleanSlug}-800.webp 800w`}
|
||||
sizes="(max-width: 640px) 60vw, 341px"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
|
||||
Reference in new issue
Block a user