Fix button alignment on article cards and the reader view
All checks were successful
Docker / build-and-push (push) Successful in 1m52s

.article-card form had margin-left: auto applied to each form
individually inside a flex row that also held the meta text (hostname,
date, unread badge) — spacing shifted depending on whether the unread
badge was present, since the auto-margin was competing with a
variable number of sibling elements instead of anchoring against a
fixed layout.

Restructured into two explicit flex groups per row — meta info on the
left, action buttons on the right, justify-content: space-between on
the container — so the buttons land at a consistent x-position
regardless of read/unread state or article title length. Same fix
applied to both the list view and the reader view's meta row.

Verified visually against both states (read and unread cards) in a
screenshot — buttons now align consistently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-07-13 19:07:08 +02:00
parent 4a6a373f1e
commit fd1566116b

View File

@@ -76,9 +76,11 @@ a:hover { text-decoration: underline; }
.article-card.unread { border-left: 2px solid var(--accent); } .article-card.unread { border-left: 2px solid var(--accent); }
.article-title { font-size: 15px; font-weight: 600; color: var(--heading); } .article-title { font-size: 15px; font-weight: 600; color: var(--heading); }
.article-title:hover { color: var(--accent); } .article-title:hover { color: var(--accent); }
.article-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 6px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } .article-meta { margin-top: 10px; display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.article-meta-info { font-family: var(--mono); font-size: 11px; color: var(--muted); display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.unread-dot { color: var(--accent); } .unread-dot { color: var(--accent); }
.article-card form { display: inline; margin-left: auto; } .article-actions { display: flex; gap: 8px; flex-shrink: 0; }
.article-actions form { display: inline; }
.empty { font-family: var(--mono); font-size: 13px; color: var(--muted); text-align: center; padding: 32px 0; } .empty { font-family: var(--mono); font-size: 13px; color: var(--muted); text-align: center; padding: 32px 0; }
@@ -87,7 +89,9 @@ article h1, article h2, article h3 { font-family: var(--sans); color: var(--head
article p { margin: 1em 0; } article p { margin: 1em 0; }
article img { max-width: 100%; border-radius: 6px; } article img { max-width: 100%; border-radius: 6px; }
article a { text-decoration: underline; } article a { text-decoration: underline; }
.reader-meta { font-family: var(--mono); font-size: 12px; color: var(--muted); margin: 8px 0 24px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } .reader-meta { margin: 8px 0 24px; display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.reader-meta-info { font-family: var(--mono); font-size: 12px; color: var(--muted); display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.reader-meta form { display: inline; }
</style> </style>
</head><body><div id="app">${body}</div></body></html>`; </head><body><div id="app">${body}</div></body></html>`;
} }
@@ -115,16 +119,20 @@ router.get('/', requireTokenQuery, (req: any, res) => {
<li class="article-card${a.read_at ? '' : ' unread'}"> <li class="article-card${a.read_at ? '' : ' unread'}">
<div class="article-title"><a href="/read/${a.id}?t=${encodeURIComponent(t)}">${escapeHtml(a.title)}</a></div> <div class="article-title"><a href="/read/${a.id}?t=${encodeURIComponent(t)}">${escapeHtml(a.title)}</a></div>
<div class="article-meta"> <div class="article-meta">
<span>${escapeHtml(new URL(a.url).hostname)}</span> <div class="article-meta-info">
<span>captured ${escapeHtml(a.captured_at.slice(0, 10))}</span> <span>${escapeHtml(new URL(a.url).hostname)}</span>
${a.read_at ? '' : '<span class="unread-dot">● unread</span>'} <span>captured ${escapeHtml(a.captured_at.slice(0, 10))}</span>
<form method="post" action="/toggle-read/${a.id}?t=${encodeURIComponent(t)}"> ${a.read_at ? '' : '<span class="unread-dot">● unread</span>'}
<input type="hidden" name="redirect" value="${escapeHtml(req.originalUrl)}"> </div>
<button type="submit" class="btn">${a.read_at ? 'mark unread' : 'mark read'}</button> <div class="article-actions">
</form> <form method="post" action="/toggle-read/${a.id}?t=${encodeURIComponent(t)}">
<form method="post" action="/delete/${a.id}?t=${encodeURIComponent(t)}" onsubmit="return confirm('Delete this article?');"> <input type="hidden" name="redirect" value="${escapeHtml(req.originalUrl)}">
<button type="submit" class="btn btn-danger">delete</button> <button type="submit" class="btn">${a.read_at ? 'mark unread' : 'mark read'}</button>
</form> </form>
<form method="post" action="/delete/${a.id}?t=${encodeURIComponent(t)}" onsubmit="return confirm('Delete this article?');">
<button type="submit" class="btn btn-danger">delete</button>
</form>
</div>
</div> </div>
</li>`).join(''); </li>`).join('');
@@ -155,16 +163,20 @@ router.get('/read/:id', requireTokenQuery, (req: any, res) => {
<article> <article>
<h1>${escapeHtml(article.title)}</h1> <h1>${escapeHtml(article.title)}</h1>
<div class="reader-meta"> <div class="reader-meta">
<a href="${escapeHtml(article.url)}" target="_blank" rel="noopener">${escapeHtml(new URL(article.url).hostname)}</a> <div class="reader-meta-info">
${article.author ? `<span>— ${escapeHtml(article.author)}</span>` : ''} <a href="${escapeHtml(article.url)}" target="_blank" rel="noopener">${escapeHtml(new URL(article.url).hostname)}</a>
<form method="post" action="/toggle-read/${article.id}?t=${encodeURIComponent(t)}"> ${article.author ? `<span>— ${escapeHtml(article.author)}</span>` : ''}
<input type="hidden" name="redirect" value="${escapeHtml(req.originalUrl)}"> </div>
<button type="submit" class="btn">${article.read_at ? 'mark unread' : 'mark read'}</button> <div class="article-actions">
</form> <form method="post" action="/toggle-read/${article.id}?t=${encodeURIComponent(t)}">
<form method="post" action="/delete/${article.id}?t=${encodeURIComponent(t)}" onsubmit="return confirm('Delete this article?');"> <input type="hidden" name="redirect" value="${escapeHtml(req.originalUrl)}">
<input type="hidden" name="redirect" value="/?t=${escapeHtml(t)}"> <button type="submit" class="btn">${article.read_at ? 'mark unread' : 'mark read'}</button>
<button type="submit" class="btn btn-danger">delete</button> </form>
</form> <form method="post" action="/delete/${article.id}?t=${encodeURIComponent(t)}" onsubmit="return confirm('Delete this article?');">
<input type="hidden" name="redirect" value="/?t=${escapeHtml(t)}">
<button type="submit" class="btn btn-danger">delete</button>
</form>
</div>
</div> </div>
${article.content_html} ${article.content_html}
</article> </article>