feat: initial release of pengzhan.dev personal editorial blog platform

- Editorial web platform built with Node.js, Express, EJS, and Vanilla CSS
- Markdown content engine with YAML frontmatter, bilingual variants, and hot-reload watcher
- Unified SQLite with FTS5 search indexing, query logging, and schema filters
- Centered editorial post layout with margin-docked left TOC and mobile bottom sheet
- WordPress-grade comment system with moderation dashboard and bulk operations under /guanli
- Complete SEO suite (JSON-LD structured data, RSS/Atom feeds, XML sitemap, robots.txt)
- CI/CD workflows for linting, security audits (Gitleaks), automated tests, and Docker releases
This commit is contained in:
2026-09-16 23:08:53 -07:00
parent ac2c4c09ef
commit 8c0b04db0e
126 changed files with 8515 additions and 3984 deletions
+84
View File
@@ -0,0 +1,84 @@
<%- include('../partials/head', { pageType: 'search' }) %>
<div class="content-container">
<div class="search-hero" style="margin-bottom: 2rem;">
<h1 style="font-size: 2.5rem; margin-bottom: 1.5rem; font-family: var(--font-serif);">Search</h1>
<form action="/search" method="GET" class="search-form-minimal" id="search-form">
<div class="search-input-wrapper">
<svg class="search-icon-svg" viewBox="0 0 24 24"><path d="M23.809 21.646l-6.205-6.205c1.167-1.605 1.857-3.579 1.857-5.711 0-5.365-4.365-9.73-9.731-9.73-5.365 0-9.73 4.365-9.73 9.73 0 5.366 4.365 9.73 9.73 9.73 2.034 0 3.923-.627 5.487-1.698l6.238 6.238 2.354-2.354zm-20.955-11.916c0-3.792 3.085-6.877 6.877-6.877s6.877 3.085 6.877 6.877-3.085 6.877-6.877 6.877c-3.793 0-6.877-3.085-6.877-6.877z"/></svg>
<input
type="search"
id="search-input"
name="q"
value="<%= query %>"
class="search-input-sleek"
placeholder="Search..."
autocomplete="off"
autofocus
>
</div>
</form>
<!-- Sleek Discovery & Recommended Searches -->
<div class="search-discovery-pills" style="margin-top: 1.25rem;">
<div class="search-pills-grid">
<% (recommended.dynamicPills || []).forEach(pill => { %>
<button type="button" class="pill search-pill-btn" data-query="<%= pill.query %>">
<%= pill.label %>
</button>
<% }) %>
<% (recommended.customPills || []).forEach(pill => { %>
<button type="button" class="pill search-pill-btn" data-query="<%= pill.query %>">
<%= pill.label %>
</button>
<% }) %>
</div>
</div>
</div>
<!-- Search Results -->
<% if (query) { %>
<div class="search-results-section" style="margin-top: 2.5rem; border-top: 1px solid var(--color-border); padding-top: 2rem;">
<div style="font-family: var(--font-sans); font-size: 0.95rem; color: var(--color-text-muted); margin-bottom: 1.5rem;">
<%= searchData.total %> result<%= searchData.total === 1 ? '' : 's' %> for "<%= query %>"
</div>
<% if (searchData.results.length > 0) { %>
<div class="search-results-list">
<% searchData.results.forEach(res => { %>
<article class="search-result-item" style="margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--color-surface-soft);">
<h2 style="font-size: 1.45rem; margin-bottom: 0.35rem; font-family: var(--font-serif);">
<a href="/<%= res.post.slug %><%= res.post.lang && res.post.lang !== 'en' && !res.post.isDefault ? `/${res.post.lang}` : '' %>" style="color: var(--color-text-main); text-decoration: none;">
<%= res.post.title %>
</a>
</h2>
<div class="post-card-meta" style="margin-bottom: 0.5rem; font-size: 0.82rem; color: var(--color-text-muted);">
<time><%= res.post.date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) %></time>
<span class="meta-sep">&mdash;</span>
<span>by <%= res.post.author %></span>
<% if (res.post.category) { %>
<span>in <%= res.post.category %></span>
<% } %>
</div>
<% if (res.snippet) { %>
<p class="search-snippet" style="font-size: 0.95rem; color: var(--color-text-secondary); line-height: 1.6;">
<%- res.snippet %>
</p>
<% } %>
</article>
<% }) %>
</div>
<% } else { %>
<p style="color: var(--color-text-muted); padding: 2rem 0; font-size: 1.05rem;">
No articles found matching your query.
</p>
<% } %>
</div>
<% } %>
</div>
<%- include('../partials/foot', { pageType: 'search' }) %>