fix: link post authors by slug, not by login

This commit is contained in:
Jakub Zych
2026-10-31 13:13:38 +01:00
parent 102da43f76
commit 1dd3176060
6 changed files with 39 additions and 17 deletions

View File

@@ -19,7 +19,7 @@ Route paths are configuration, not files. The layer's page components live in `a
- **Post list** (`journal-index`): paginated with `?page=N`. A non-numeric page or a page past the last one answers 404.
- **Post** (`journal-post`): title, date, reading time, categories, author, featured image, body, previous and next links, and related posts. When the API returns no related posts (a post without a category), the latest posts are shown instead.
- **Category** (`journal-category`): name, description and the category's rich HTML content above its posts. Categories are always requested with `include_empty=1`, so a category without posts still has a page. An unknown category answers 404, which makes the route safe as a root-level catch-all such as `/:category`.
- **Tag** (`journal-tag`) and **author** (`journal-author`) pages. An author resolves by author slug, or by login once the author has a published post; a login URL sets the canonical to the slug URL.
- **Tag** (`journal-tag`) and **author** (`journal-author`) pages. An author resolves by author slug, or by login once the author has a published post; a login URL sets the canonical to the slug URL. Post meta links to the author by `author.slug` (sm-journal-plugin v0.3.1+), falling back to the login on older APIs.
- **Search** (`journal-search`, opt-in): a results page for `?q=` and the `JournalSearchBox` header component with suggestions as the visitor types. Off until the app sets `journal.routes.search`. See [Search](#search).
- **RSS**: `/rss.xml` proxies the API feed, and list and post pages link it in the head.
- **SEO**: per-page title and description, Open Graph tags (`og:image` from the featured image, article times), canonical URLs that keep `?page=N`, robots and `sitemap.xml` through `@nuxtjs/seo`.

View File

@@ -11,9 +11,15 @@ const links = useJournalLinks()
const date = computed(() => formatJournalDate(props.post.published_at, localeProperties.value.language ?? 'en'))
const authorName = computed(() => {
const a = props.post.author
return a ? `${a.first_name} ${a.last_name}`.trim() || a.login : ''
return a ? `${a.first_name} ${a.last_name}`.trim() || a.slug || a.login : ''
})
// By slug, so the link does not carry the login (often an email address).
// An API before sm-journal-plugin v0.3.1 sends no slug; the login still
// resolves there.
const authorHref = computed(() => {
const a = props.post.author
return a ? links.authorLink(a.slug || a.login) : null
})
const authorHref = computed(() => (props.post.author ? links.authorLink(props.post.author.login) : null))
</script>
<template>

View File

@@ -8,6 +8,8 @@ export interface JournalAuthorRef {
first_name: string
last_name: string
login: string
/** The author slug, else the login (sm-journal-plugin v0.3.1+). */
slug?: string
}
/** A category or tag reference embedded in a post row. */

View File

@@ -1,6 +1,6 @@
{
"name": "vue-journal-layer",
"version": "0.7.0",
"version": "0.7.1",
"private": true,
"type": "module",
"main": "./nuxt.config.ts",

View File

@@ -29,6 +29,7 @@
# Author (slug and login resolve) and tag pages
200 /blog/author/jane-doe contains=Jane Doe
200 /blog/author/jdoe contains=<link rel="canonical" href="https://acme.test/blog/author/jane-doe"
200 /blog/hello-acme contains=href="/blog/author/jane-doe" notcontains=/blog/author/jdoe
404 /blog/author/nobody
200 /blog/tag/release contains=Hello Acme
404 /blog/tag/nope

View File

@@ -16,7 +16,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -62,7 +63,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -95,7 +97,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -128,7 +131,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -161,7 +165,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -194,7 +199,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -227,7 +233,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -260,7 +267,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -293,7 +301,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -326,7 +335,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -359,7 +369,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -392,7 +403,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{
@@ -447,7 +459,8 @@
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"login": "jdoe"
"login": "jdoe",
"slug": "jane-doe"
},
"categories": [
{