refactor: align views with upstream @indiekit/frontend patterns

- Extract ~560 lines of inline CSS to external assets/styles.css
- Create intermediate layout (layouts/blogroll.njk) for CSS loading
- Use section(), badge(), button(), prose() macros instead of raw HTML
- Remove custom page headers (document.njk heading() handles via title/parent)
- Add parent breadcrumb navigation to all sub-pages
- Add consumeFlashMessage() to dashboard and sources controllers
- Rename CSS class prefix from br-* to blogroll-* for clarity
- Use upstream CSS custom properties without fallback values
- Fix Microsub orphan detection (soft-delete unsubscribed blogs)
- Fix upsert to conditionally set microsub fields (avoid path conflicts)
- Skip soft-deleted blogs during clear-and-resync

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricardo
2026-02-12 18:42:27 +01:00
parent 87851bade2
commit 4ad4c13bbc
14 changed files with 722 additions and 925 deletions

342
assets/styles.css Normal file
View File

@@ -0,0 +1,342 @@
/* Blogroll endpoint styles */
/* Stats grid */
.blogroll-stats {
display: grid;
gap: var(--space-s);
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.blogroll-stat {
background: var(--color-background);
border-radius: var(--radius-s);
padding: var(--space-s);
text-align: center;
}
.blogroll-stat dt {
color: var(--color-text-secondary);
font-size: var(--step--1);
margin-block-end: var(--space-2xs);
}
.blogroll-stat dd {
font-size: var(--step-0);
font-weight: var(--font-weight-semibold);
margin: 0;
}
.blogroll-stat dd.blogroll-stat--error {
color: var(--color-error);
}
/* Quick links (button row) */
.blogroll-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-s);
margin-block-start: var(--space-m);
}
/* List (shared between blogs, sources, errors, dashboard lists) */
.blogroll-list {
display: flex;
flex-direction: column;
gap: var(--space-s);
list-style: none;
margin: 0;
padding: 0;
}
.blogroll-list__item {
align-items: flex-start;
background: var(--color-offset);
border-radius: var(--radius-m);
display: flex;
flex-wrap: wrap;
gap: var(--space-s);
justify-content: space-between;
padding: var(--space-m);
}
.blogroll-list__item--compact {
padding: var(--space-xs) var(--space-s);
}
.blogroll-list__item--pinned {
border-inline-start: 3px solid var(--color-accent);
}
.blogroll-list__item--hidden {
opacity: 0.6;
}
/* Item content (left side) */
.blogroll-item__info {
flex: 1;
min-inline-size: 200px;
}
.blogroll-item__title {
font-size: var(--step-0);
font-weight: var(--font-weight-semibold);
margin: 0 0 var(--space-2xs);
}
.blogroll-item__title a {
color: inherit;
text-decoration: none;
}
.blogroll-item__title a:hover {
text-decoration: underline;
}
.blogroll-item__meta {
align-items: center;
color: var(--color-text-secondary);
display: flex;
flex-wrap: wrap;
font-size: var(--step--1);
gap: var(--space-xs);
}
.blogroll-item__url {
color: var(--color-accent);
font-family: monospace;
font-size: var(--step--2);
margin-block-start: var(--space-2xs);
word-break: break-all;
}
.blogroll-item__error {
color: var(--color-error);
font-size: var(--step--1);
margin-block-start: var(--space-2xs);
}
/* Item actions (right side) */
.blogroll-item__actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
}
/* Filters */
.blogroll-filters {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: var(--space-s);
}
.blogroll-filter-select {
appearance: none;
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--radius-s);
font-size: var(--step--1);
min-inline-size: 150px;
padding: var(--space-2xs) var(--space-s);
}
/* Form fields */
.blogroll-form {
max-inline-size: 600px;
}
.blogroll-field {
display: flex;
flex-direction: column;
gap: var(--space-2xs);
margin-block-end: var(--space-m);
}
.blogroll-field label {
font-weight: var(--font-weight-semibold);
}
.blogroll-field-hint {
color: var(--color-text-secondary);
font-size: var(--step--1);
}
.blogroll-field input,
.blogroll-field select,
.blogroll-field textarea {
appearance: none;
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--radius-s);
font-size: var(--step--1);
padding: var(--space-2xs) var(--space-s);
width: 100%;
}
.blogroll-field textarea {
min-block-size: 100px;
}
.blogroll-field input:focus,
.blogroll-field select:focus,
.blogroll-field textarea:focus {
border-color: var(--color-accent);
outline: 2px solid var(--color-accent);
outline-offset: 1px;
}
.blogroll-field--inline {
align-items: center;
flex-direction: row;
gap: var(--space-s);
}
.blogroll-field--inline input[type="checkbox"] {
appearance: auto;
cursor: pointer;
width: auto;
}
/* API list */
.blogroll-api-list {
display: flex;
flex-direction: column;
gap: var(--space-xs);
list-style: none;
margin: 0;
padding: 0;
}
.blogroll-api-list li {
background: var(--color-background);
border-radius: var(--radius-s);
font-size: var(--step--1);
padding: var(--space-xs) var(--space-s);
}
.blogroll-api-list code {
color: var(--color-accent);
font-weight: var(--font-weight-semibold);
}
/* Feed items (inside blog-edit) */
.blogroll-items-list {
display: flex;
flex-direction: column;
gap: var(--space-xs);
list-style: none;
margin: 0;
padding: 0;
}
.blogroll-items-list li {
background: var(--color-offset);
border-radius: var(--radius-s);
padding: var(--space-xs) var(--space-s);
}
.blogroll-items-list .blogroll-item__title {
font-size: var(--step--1);
margin: 0;
}
.blogroll-items-list .blogroll-item__meta {
font-size: var(--step--2);
}
/* Feed discovery (blog-edit new) */
.blogroll-discover {
background: var(--color-offset);
border-radius: var(--radius-m);
margin-block-end: var(--space-m);
padding: var(--space-m);
}
.blogroll-discover .blogroll-field {
margin-block-end: var(--space-s);
}
.blogroll-discover__input {
display: flex;
gap: var(--space-s);
}
.blogroll-discover__input input {
appearance: none;
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--radius-s);
flex: 1;
font-size: var(--step--1);
padding: var(--space-2xs) var(--space-s);
}
.blogroll-discover__result {
background: var(--color-background);
border-radius: var(--radius-s);
font-size: var(--step--1);
margin-block-start: var(--space-s);
padding: var(--space-s);
}
.blogroll-discover__result--error {
color: var(--color-error);
}
.blogroll-discover__result--success {
color: var(--color-success);
}
.blogroll-discover__feeds {
display: flex;
flex-direction: column;
gap: var(--space-xs);
list-style: none;
margin: var(--space-xs) 0 0;
padding: 0;
}
.blogroll-discover__feed {
align-items: center;
background: var(--color-offset);
border-radius: var(--radius-s);
cursor: pointer;
display: flex;
gap: var(--space-s);
padding: var(--space-xs);
}
.blogroll-discover__feed:hover {
opacity: 0.8;
}
.blogroll-discover__feed-url {
flex: 1;
font-family: monospace;
font-size: var(--step--2);
word-break: break-all;
}
.blogroll-discover__feed-type {
background: var(--color-accent);
border-radius: var(--radius-s);
color: var(--color-on-accent);
font-size: var(--step--2);
padding: var(--space-3xs) var(--space-2xs);
text-transform: uppercase;
}
/* Empty state */
.blogroll-empty {
color: var(--color-text-secondary);
font-size: var(--step--1);
padding: var(--space-m);
text-align: center;
}
/* Divider */
.blogroll-divider {
border: none;
border-block-start: 1px solid var(--color-border);
margin: var(--space-m) 0;
}