Files
vue-journal-layer/app/utils/types.ts
Jakub Zych adcea4d538 feat: category neighbours on the post page
The post page links previous_in_category and next_in_category (the
Journal's opt-in scored related mode) as server-rendered links with the
title in the anchor text when the Journal answers one, else the
site-wide previous_post and next_post (postNeighbours). Types, a fixture
post and expectation lines for both cases.
2026-11-04 14:15:22 +01:00

200 lines
5.5 KiB
TypeScript

// Wire types of the anonymous Journal API (/_journal/api/v1). Field names
// mirror the Go serializers exactly (snake_case); a field is nullable where
// Go can emit null. Types only, so `node --test` can import any util.
/** The author object embedded in a post row. `null` when the post has none. */
export interface JournalAuthorRef {
id: number
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. */
export interface JournalTermRef {
id: number
name: string
slug: string
}
export interface JournalFeaturedImage {
id: number
url: string
file_name: string
file_size: number
}
/** One post row, as GET /posts and GET /authors/{author}/posts list it. */
export interface JournalPost {
id: number
title: string
slug: string
excerpt: string | null
content: string | null
content_html: string | null
reading_time: number
published: boolean
/** UTC, "2006-01-02T15:04:05+00:00". */
published_at: string | null
is_pinned: boolean
metadata: unknown
sources: unknown
author: JournalAuthorRef | null
categories: JournalTermRef[]
tags: JournalTermRef[]
featured_images: JournalFeaturedImage[]
featured_image_url: string | null
translations: unknown
created_at: string | null
updated_at: string | null
}
/**
* One row of GET /posts?fields=suggest: a subset of JournalPost for a search
* box. `excerpt` is plain text of about 160 characters.
*/
export interface JournalPostSuggestion {
id: number
title: string
slug: string
excerpt: string
featured_image_url: string | null
published_at: string | null
}
/** previous_post / next_post on GET /posts/{slug}. */
export interface JournalPostSibling {
id: number
title: string
slug: string
}
/** GET /posts/{slug} data: the post plus its neighbours and related posts. */
export interface JournalPostDetail extends JournalPost {
previous_post: JournalPostSibling | null
next_post: JournalPostSibling | null
/**
* Latest 3 in the same categories (empty when the post has no category);
* with the Journal's related_mode scored, 3 to 6 ranked by shared tags
* and categories.
*/
related_posts: JournalPost[]
/**
* The neighbours by publish time within the post's first category, in
* the direction of previous_post and next_post. Only with the Journal's
* opt-in related_mode scored; absent otherwise.
*/
previous_in_category?: JournalPostSibling | null
next_in_category?: JournalPostSibling | null
/**
* The Journal H1 setting (newer Journal APIs): true while stored
* content has its H1 headings converted to H2, so the post title is the
* page's H1. Absent on an older Journal, which counts as true.
*/
content_h1_to_h2?: boolean
}
/** One node of the GET /categories tree. */
export interface JournalCategory {
id: number
name: string
slug: string
description: string | null
/** Rich HTML body of the category page, stored as is; null when unset. */
content: string | null
/**
* The Journal H1 setting on this row (newer Journal APIs): true while an
* h1 in content becomes h2. Absent on an older Journal, which counts as
* true.
*/
content_h1_to_h2?: boolean
parent_id: number | null
nest_depth: number | null
post_count: number
children?: JournalCategory[]
}
export interface JournalTag {
id: number
name: string
slug: string
post_count: number
}
/**
* GET /authors/{author} data. `slug` is the canonical identifier: the author
* slug when set, otherwise the login. Missing names are "".
*/
export interface JournalAuthor {
id: number
slug: string
first_name: string
last_name: string
}
export interface JournalPageMeta {
current_page: number
last_page: number
per_page: number
total: number
}
/** The paginated envelope of GET /posts and GET /authors/{author}/posts. */
export interface JournalPage<T> {
data: T[]
meta: JournalPageMeta
}
/** The single-object envelope ({ data }) of show and list endpoints. */
export interface JournalItem<T> {
data: T
}
/** Route paths per Journal view; `false` disables the view. */
export interface JournalRoutes {
index: string | false
post: string | false
category: string | false
tag: string | false
author: string | false
/** Search results page; the term is the `q` query parameter. Off by default. */
search: string | false
}
export interface JournalPerPage {
index: number
category: number
tag: number
author: number
search: number
}
/** Options of the `journal` config key in nuxt.config. */
export interface JournalModuleOptions {
routes: JournalRoutes
perPage: JournalPerPage
/** "<column> <asc|desc>", column one of published_at, created_at, updated_at, title, id. */
sort: string
/** How many related posts a post page shows. */
relatedCount: number
/** Whether /rss.xml is served and linked. */
rss: boolean
/**
* The post page title's element: 'auto' (default) follows the Journal H1
* setting (h1 while content_h1_to_h2 is on, p while it is off), 'h1' or
* 'p' force it. NUXT_PUBLIC_JOURNAL_POST_TITLE_TAG at runtime.
*/
postTitleTag: JournalPostTitleTag
/**
* Whether the journal pages render <SeoBreadcrumbs> at the top of their
* content (default true). false for a site whose layout places them.
* NUXT_PUBLIC_JOURNAL_BREADCRUMBS at runtime.
*/
breadcrumbs: boolean
}
/** journal.postTitleTag. */
export type JournalPostTitleTag = 'auto' | 'h1' | 'p'