From c95db3a044586f658e615584d9c630c4e7b12bca Mon Sep 17 00:00:00 2001 From: Matthias G Date: Sat, 1 Aug 2026 13:04:08 +0200 Subject: [PATCH] Give the archive its signature back - the plate carries project colour on the lead image, in the cards and in every row, not only when an entry lacks an image - entries no longer fade in one after another, the page is readable at once - detail pages lead on: previous, next and more from the project - the month band shows from the first month, the nav item hides without one - side cards and row teasers get room instead of ellipsis - admin opens on drafts, scheduled and published plus what was edited last, and its tables use the full width --- messages/de.json | 10 +++- messages/en.json | 10 +++- src/app/(site)/[project]/[entry]/page.tsx | 13 ++++- src/app/(site)/page.tsx | 4 +- src/app/admin/layout.tsx | 2 +- src/app/admin/page.tsx | 59 +++++++++++++++++++++-- src/app/globals.css | 1 + src/components/archive/ArchiveMonths.tsx | 2 +- src/components/archive/EntryRow.tsx | 40 ++++++--------- src/components/archive/FeatureEntry.tsx | 35 ++++++++------ src/components/archive/PostDetail.tsx | 50 +++++++++++++++++-- src/components/archive/ProjectArchive.tsx | 2 +- src/components/archive/SideEntries.tsx | 33 +++++++------ src/components/layout/SiteNav.tsx | 25 ++++++---- src/components/layout/Wrap.tsx | 9 +++- src/components/ui/Plate.tsx | 5 +- src/data/repositories/archive.ts | 48 +++++++++++++++++- 17 files changed, 264 insertions(+), 84 deletions(-) diff --git a/messages/de.json b/messages/de.json index 10194b2..8ebd4bc 100644 --- a/messages/de.json +++ b/messages/de.json @@ -50,7 +50,11 @@ "back": "Alle Einträge von {project}", "details": "Angaben", "readingTime": "{minutes} Min. Lesezeit", - "emptyTitle": "Noch kein Inhalt" + "emptyTitle": "Noch kein Inhalt", + "previous": "Vorheriger Eintrag", + "next": "Nächster Eintrag", + "nearby": "Weiter im Projekt", + "more": "Mehr aus {project}" }, "block": { "before": "Vorher", @@ -675,7 +679,9 @@ }, "publishTitle": "Veröffentlichen", "publishHint": "Kein Zugang darf veröffentlichen. Das bleibt an Konten gebunden." - } + }, + "recentEntries": "Zuletzt bearbeitet", + "allEntries": "Alle Beiträge" }, "filter": { "type": "Art", diff --git a/messages/en.json b/messages/en.json index 68b9836..daf65ec 100644 --- a/messages/en.json +++ b/messages/en.json @@ -50,7 +50,11 @@ "back": "All entries of {project}", "details": "Details", "readingTime": "{minutes} min read", - "emptyTitle": "No content yet" + "emptyTitle": "No content yet", + "previous": "Previous entry", + "next": "Next entry", + "nearby": "More in this project", + "more": "More from {project}" }, "block": { "before": "Before", @@ -675,7 +679,9 @@ }, "publishTitle": "Publishing", "publishHint": "No access may publish. That stays bound to accounts." - } + }, + "recentEntries": "Recently edited", + "allEntries": "All entries" }, "filter": { "type": "Type", diff --git a/src/app/(site)/[project]/[entry]/page.tsx b/src/app/(site)/[project]/[entry]/page.tsx index 8591a9f..85c716c 100644 --- a/src/app/(site)/[project]/[entry]/page.tsx +++ b/src/app/(site)/[project]/[entry]/page.tsx @@ -6,6 +6,7 @@ import { ProjectArchive } from '~/components/archive/ProjectArchive' import { findPostWithBlocks, listArchiveMonths, + loadPostNeighbours, listPostTypeCounts, listPostsForArchive, loadArchiveStats, @@ -60,7 +61,17 @@ export default async function EntryPage({ params, searchParams }: Props) { notFound() } - return + const neighbours = await loadPostNeighbours({ + scope, + projectSlug: detail.project.slug, + postId: detail.post.id, + publishAt: detail.post.publishAt, + limit: coverCount, + }) + + const nearbyCovers = await loadPostCovers(neighbours.more.map(item => item.id)) + + return } const query = await searchParams diff --git a/src/app/(site)/page.tsx b/src/app/(site)/page.tsx index 43b0b01..1e7ea91 100644 --- a/src/app/(site)/page.tsx +++ b/src/app/(site)/page.tsx @@ -24,7 +24,7 @@ import type { ViewerScope } from '~/domain/types' const scope: ViewerScope = 'internal' -const sideCount = 3 +const sideCount = 2 const sideThreshold = 4 @@ -101,7 +101,7 @@ export default async function OverviewPage({ searchParams }: { searchParams: Pro {lead ? (
- +
) : null} diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index b5d19e5..e916f88 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -29,7 +29,7 @@ export default async function AdminLayout({ children }: { children: ReactNode }) mark={} >
- {children} + {children}
) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index e181a5c..59539bf 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -2,11 +2,13 @@ import Link from 'next/link' import { getTranslations } from 'next-intl/server' import { TbArrowNarrowRight, TbPhoto, TbPlus } from 'react-icons/tb' import { AdminHeading } from '~/components/admin/AdminHeading' +import { EntryStatusChip } from '~/components/admin/EntryStatusChip' import { AdminNotice } from '~/components/admin/AdminNotice' import { ghostButtonClass, quietLinkClass } from '~/components/admin/styles' -import { projectStyle, projectSurface } from '~/components/ui/Plate' +import { Plate, projectStyle, projectSurface } from '~/components/ui/Plate' import { SectionLabel } from '~/components/ui/SectionLabel' import { listClients } from '~/data/repositories/clients' +import { listEntries } from '~/data/repositories/entries' import { listAllProjects, listBrands } from '~/data/repositories/projects' import { listUsers } from '~/data/repositories/users' import { listProjectsForUser } from '~/data/repositories/user-roles' @@ -15,6 +17,8 @@ import { adminBrandsPath, adminClientsPath, adminNewEntryPath, + adminEntriesPath, + adminEntryPath, adminNewProjectPath, adminProjectEntriesPath, adminProjectPath, @@ -25,6 +29,8 @@ import { isAdmin } from '~/lib/auth-access' import { requireAdminArea } from '~/lib/auth-guards' import { adminMediaPath } from '~/lib/auth-routes' +const recentCount = 5 + export default async function AdminPage() { const viewer = await requireAdminArea() const t = await getTranslations('admin') @@ -35,9 +41,24 @@ export default async function AdminPage() { const brands = manages ? await listBrands() : [] const users = manages ? await listUsers() : [] const clients = manages ? await listClients() : [] + const projectIds = manages ? undefined : projects.map(project => project.id) + + const [drafts, scheduled, published, recent] = await Promise.all([ + listEntries({ projectIds, status: 'draft', page: 1, perPage: 1 }), + listEntries({ projectIds, status: 'scheduled', page: 1, perPage: 1 }), + listEntries({ projectIds, status: 'published', page: 1, perPage: 1 }), + listEntries({ projectIds, page: 1, perPage: recentCount }), + ]) + + const entryTiles = [ + { href: adminEntriesPath, label: e('status.draft'), value: drafts.total }, + { href: adminEntriesPath, label: e('status.scheduled'), value: scheduled.total }, + { href: adminEntriesPath, label: e('status.published'), value: published.total }, + ] const tiles = manages ? [ + ...entryTiles, { href: adminProjectsPath, label: t('nav.projects'), value: projects.length }, { href: adminBrandsPath, label: t('nav.brands'), value: brands.length }, { href: adminUsersPath, label: t('nav.users'), value: users.length }, @@ -47,7 +68,7 @@ export default async function AdminPage() { value: clients.filter(client => client.revokedAt === null).length, }, ] - : [] + : entryTiles return (
@@ -72,9 +93,9 @@ export default async function AdminPage() { /> {tiles.length > 0 ? ( -
    +
      {tiles.map(tile => ( -
    • +
    • {tile.label} @@ -86,6 +107,36 @@ export default async function AdminPage() {
    ) : null} + {recent.items.length > 0 ? ( +
    + {t('allEntries')}}> + {t('recentEntries')} + +
      + {recent.items.map(item => ( +
    • + + + {item.title} + + +
    • + ))} +
    +
    + ) : null} +
    {t('projectsMeta', { count: projects.length })}}> {t('yourProjects')} diff --git a/src/app/globals.css b/src/app/globals.css index 1610f09..87664ca 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -17,6 +17,7 @@ --color-shot-1: var(--color-rule); --color-shot-2: var(--color-ink-3); --container-page: 63rem; + --container-wide: 80rem; --container-read: 38rem; --font-display: var(--font-archivo), system-ui, sans-serif; --font-body: var(--font-source-serif), Georgia, serif; diff --git a/src/components/archive/ArchiveMonths.tsx b/src/components/archive/ArchiveMonths.tsx index 05d1a03..4cebe45 100644 --- a/src/components/archive/ArchiveMonths.tsx +++ b/src/components/archive/ArchiveMonths.tsx @@ -37,7 +37,7 @@ function group(months: ArchiveMonth[]): YearGroup[] { export function ArchiveMonths({ months, hrefFor, activeYear, activeMonth }: ArchiveMonthsProps) { const t = useTranslations('archive') - if (months.length <= 1) { + if (months.length === 0) { return null } diff --git a/src/components/archive/EntryRow.tsx b/src/components/archive/EntryRow.tsx index 3989a80..5932fef 100644 --- a/src/components/archive/EntryRow.tsx +++ b/src/components/archive/EntryRow.tsx @@ -1,11 +1,10 @@ import Link from 'next/link' -import type { CSSProperties } from 'react' import { useTranslations } from 'next-intl' import { AuthorMark } from '~/components/ui/AuthorMark' import { Badge } from '~/components/ui/Badge' import { EntryDate } from '~/components/ui/EntryDate' import { Highlight } from '~/components/ui/Highlight' -import { entryMark, entryNumber, projectStyle, projectSurface } from '~/components/ui/Plate' +import { Plate } from '~/components/ui/Plate' import { postPath } from '~/lib/routes' import type { PostListItem } from '~/data/repositories/posts' @@ -17,49 +16,42 @@ export type EntryRowProps = { className?: string } -const stagger = 40 - -const maxStagger = 12 - -export function EntryRow({ item, index = 0, showCode = true, highlight, className }: EntryRowProps) { +export function EntryRow({ item, showCode = true, highlight, className }: EntryRowProps) { const t = useTranslations('entry') - const delay = `${Math.min(index, maxStagger) * stagger}ms` - const style: CSSProperties = { ...projectStyle(item.projectColor), animationDelay: delay } const source = { code: item.projectCode, slug: item.projectSlug } return ( -