友链卡片

This commit is contained in:
riseforever2026
2026-10-01 11:16:32 +08:00
parent 6a1d4a92b8
commit 8136b6ee4c
6 changed files with 31 additions and 11 deletions
+20 -7
View File
@@ -12,7 +12,7 @@ const sections: Section[] = [{ name: '其他', links: [], contentLines: [] }];
let section = sections[0];
let shortcodeDepth = 0;
const escapeHtml = (value: string) => value.replace(/[&<>"']/g, (character) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[character]!);
const renderNestedCard = (link: Link) => `<div class="links-nested-card"><article class="link-card"><img class="link-avatar" src="${escapeHtml(link.avatar)}" alt="" width="32" height="32" loading="lazy"><div class="link-copy"><h3><a class="link-name" data-text-link href="${escapeHtml(link.url)}" target="_blank" rel="noreferrer">${escapeHtml(link.name)}</a></h3><p class="link-description">${escapeHtml(link.description)}</p></div></article></div>`;
const renderNestedCard = (link: Link) => `<div class="links-nested-card"><a class="link-card" href="${escapeHtml(link.url)}" target="_blank" rel="noreferrer" title="${escapeHtml(link.description)}"><img class="link-avatar" src="${escapeHtml(link.avatar)}" alt="" width="32" height="32" loading="lazy"><div class="link-copy"><h3><span class="link-name" data-text-link>${escapeHtml(link.name)}</span></h3><p class="link-description">${escapeHtml(link.description)}</p></div></a></div>`;
for (const line of (page.body ?? '').split(/\r?\n/)) {
if (/^\s*\[(?:collapse|hint|tip|tabs)\b[^\]]*\]\s*$/i.test(line)) {
shortcodeDepth += 1;
@@ -45,16 +45,16 @@ if (!sections[0].links.length && !sections[0].contentLines.some((line) => line.t
<Fragment slot="masthead"><Masthead banner={{ title: page.data.title, subtitle: page.data.description, image: page.data.hero ?? page.data.cover, textTone: page.data.textTone }} /></Fragment>
<section class="links-shell" aria-label="友链内容">
{sections.map((group) => <section class="links-category" aria-labelledby={`links-category-${group.name}`}>
<h2 id={`links-category-${group.name}`}>{group.name}</h2>
<h2 class="prose-heading" id={`links-category-${group.name}`}>{group.name}</h2>
{group.links.length > 0 && <ul class="links-list">
{group.links.map((link) => <li class="link-entry">
<article class="link-card">
<a class="link-card" href={link.url} target="_blank" rel="noreferrer" title={link.description}>
<img class="link-avatar" src={link.avatar} alt="" width="32" height="32" loading="lazy" />
<div class="link-copy">
<h3><a class="link-name" data-text-link href={link.url} target="_blank" rel="noreferrer">{link.name}</a></h3>
<h3><span class="link-name" data-text-link>{link.name}</span></h3>
<p class="link-description">{link.description}</p>
</div>
</article>
</a>
</li>)}
</ul>}
{group.contentLines.some((line) => line.trim()) && <div class="links-content prose" set:html={renderMarkdown(group.contentLines.join('\n'))}></div>}
@@ -65,18 +65,31 @@ if (!sections[0].links.length && !sections[0].contentLines.some((line) => line.t
<style>
.links-shell { width: min(calc(100% - 2.5rem), var(--article-content-width)); margin-inline: auto; padding-block: 2rem 5rem; }
.links-category + .links-category { margin-top: 2rem; }
.links-category h2 { margin: 0 0 .75rem; padding-bottom: .5rem; border-bottom: 1px solid var(--color-border); font-size: 1.2rem; font-weight: 600; }
/* 分组标题复用文章正文的二级标题渲染(见 styles/content.css 的 .prose-heading)。 */
.links-shell > .links-category:first-child > h2 { margin-top: 0; }
.links-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .25rem; margin: 0; padding: 0; list-style: none; }
.link-entry { min-width: 0; margin: 0; }
.link-card { display: flex; align-items: flex-start; gap: .75rem; min-width: 0; padding: .75rem .65rem; border-radius: var(--radius-sm); transition: background-color var(--transition-fast); }
.link-card { display: flex; align-items: flex-start; gap: .75rem; width: 100%; height: 100%; min-width: 0; padding: .75rem .65rem; border-radius: var(--radius-sm); color: inherit; transition: background-color var(--transition-fast); }
.link-entry:hover { background: var(--color-archive-hover); }
.link-avatar { width: 2rem; height: 2rem; flex: 0 0 2rem; border-radius: 50%; object-fit: cover; background: var(--color-surface); }
.link-copy { min-width: 0; line-height: 1.7; overflow-wrap: anywhere; }
.link-copy h3 { margin: 0; font-size: 1rem; font-weight: 600; line-height: 1.45; }
.link-name { display: inline; color: var(--color-accent); text-decoration: none; }
:global(:root[data-theme='sunset']) .link-name { color: color-mix(in srgb, var(--color-accent-strong) 82%, black); }
/* 整张卡片可点击,悬停卡片时展开标题的下划线反馈。 */
.link-card:is(:hover, :focus-visible) .link-name::after { transform: scaleX(1); transform-origin: left center; }
.link-description, .link-note { margin: .2rem 0 0; color: color-mix(in srgb, var(--color-text) 82%, var(--color-muted)); font-size: .88rem; }
.link-note { font-size: .86rem; }
/* 简介最多两行并预留两行高度,使所有卡片宽高一致。 */
.link-description {
display: -webkit-box;
min-height: calc(2 * 1.7em);
overflow: hidden;
line-height: 1.7;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
}
.links-content { margin-top: 2rem; }
.links-content .links-nested-card { margin-block: 1rem; }
.links-content .link-card { margin: 0; }
+7 -3
View File
@@ -29,7 +29,9 @@
.prose :where(p, li, blockquote, td, th) { overflow-wrap: anywhere; }
.prose :where(h1, h2, h3, h4) {
/* `.prose-heading` 让正文之外的页面(如友链页的分组标题)复用文章标题的渲染模式。 */
.prose :where(h1, h2, h3, h4),
.prose-heading {
margin: 1.8em 0 0.65em;
font-family: var(--reading-font-family);
font-weight: 600;
@@ -45,12 +47,14 @@
text-wrap: balance;
}
.prose h2 {
.prose h2,
.prose-heading {
font-size: 1.65rem;
}
.prose h2,
.prose h3 {
.prose h3,
.prose-heading {
display: block !important;
width: fit-content;
max-width: 100%;
+1 -1
View File
@@ -40,7 +40,7 @@ body {
line-height: 1.7;
}
:where(.prose h2, .prose h3, .prose h4) {
:where(.prose h2, .prose h3, .prose h4, .prose-heading) {
scroll-margin-top: calc(var(--nav-height) + 1rem);
}
@@ -0,0 +1,2 @@
{"sequence":1,"timestamp":"2026-10-01T11:06:51.6701298+08:00","event_type":"state_change","task_id":"46d8b446ce82482d--pwsh-1","session_id":"","state":"running","runtime_state":"alive"}
{"sequence":2,"timestamp":"2026-10-01T11:15:00.2344245+08:00","event_type":"state_change","task_id":"46d8b446ce82482d--pwsh-1","session_id":"","state":"cancelled","runtime_state":"exited","error_code":"job_failed"}
@@ -0,0 +1 @@
{"schema_version":1,"task_id":"46d8b446ce82482d--pwsh-1","job_id":"pwsh-1","session_id":"","state":"cancelled","runtime_state":"exited","runtime_lease_until":"0001-01-01T00:00:00Z","version":99,"created_at":"2026-10-01T11:06:51.6701298+08:00","updated_at":"2026-10-01T11:15:00.2344245+08:00","error_code":"job_failed"}