Fix button alignment on article cards and the reader view
All checks were successful
Docker / build-and-push (push) Successful in 1m52s
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:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user