fix: path-form listing routes use the SEO module's resolved pagination
- journalPaginationSource answers runtimeConfig.public.seo.pagination (the SEO module's option with its defaults), else the raw seo.pagination - the module computes the paged routes inside its extendPages callback, so an app with only form: 'path' gets /blog/strona/:page and /blog/page/:page (L4 WR-03)
This commit is contained in:
@@ -240,7 +240,7 @@ The page head comes from the CMS: vue-seo-layer resolves every page through the
|
||||
- **ItemList.** The index, category, tag and author pages add an `ItemList` node (`@id` `<page URL>#itemlist`, one `ListItem` with the absolute URL of each post shown, positions continuing across pages) through vue-seo-layer's `useSchemaNode` (`useJournalItemList`), so the page keeps one JSON-LD script.
|
||||
- **Body links.** `JournalPostBody` renders the body through `renderJournalBody` in the UI-SPEC L7 order: the sanitizer (with H1 demotion), vue-seo-layer's `autolink` with the resolve answer's `autolinks` and `autolink_max` (first occurrence, whole words, never in headings, links, code, figure captions or buttons, never to the page itself), `applyOutbound` with the answer's `outbound` (the editor's `rel` kept, `nofollow` or `sponsored` from the domain lists; `target="_blank"`, `rel="noopener"` and a `<span class="seo-visually-hidden">` hint only while `new_tab` is on, the hint while vue-seo-layer's `seo.links.newTabHint` is on, its default) and `imageAttrs` (`loading="lazy"`, `decoding="async"`, stored width, height and `alt=""` kept). Internal links are untouched.
|
||||
- **Breadcrumbs.** Post, index, category, tag and author pages render `<SeoBreadcrumbs>` at the top of their content while `journal.breadcrumbs` is on. A site that places the trail in its layout sets it to `false` and renders one `<SeoBreadcrumbs>` there; vue-seo-layer renders one trail per page either way, and the `BreadcrumbList` node is the CMS's.
|
||||
- **Pagination.** `JournalPagination` renders `<SeoPagination class-prefix="journal-pagination">` with the journal's labels: the classes `journal-pagination`, `__prev`, `__pages`, `__current` and `__next` stay, plus page 1, the last page and `__gap` items, `aria-label`s and head `rel="prev"`/`rel="next"` links. Links follow the CMS's pagination form; with `seo.pagination.form: 'path'` in the app's config the module registers `<listing>/<segment>/:page` routes for the index, category, tag and author pages.
|
||||
- **Pagination.** `JournalPagination` renders `<SeoPagination class-prefix="journal-pagination">` with the journal's labels: the classes `journal-pagination`, `__prev`, `__pages`, `__current` and `__next` stay, plus page 1, the last page and `__gap` items, `aria-label`s and head `rel="prev"`/`rel="next"` links. Links follow the CMS's pagination form; with `seo.pagination.form: 'path'` in the app's config the module registers `<listing>/<segment>/:page` routes for the index, category, tag and author pages. The segments follow vue-seo-layer's resolved `seo.pagination` option, defaults included (`pl` `strona`, `en` `page`), the same ones its pagination links use, so an app that sets only `form: 'path'` gets `/blog/strona/:page` and `/blog/page/:page`.
|
||||
|
||||
### Headings
|
||||
|
||||
|
||||
@@ -88,6 +88,24 @@ export function journalPagedRouteName(key: keyof JournalRoutes, segment: string)
|
||||
return `${JOURNAL_ROUTE_NAMES[key]}-page-${segment}`
|
||||
}
|
||||
|
||||
/**
|
||||
* The pagination option the journal's path-form routes follow (P33-71, L4
|
||||
* WR-03): vue-seo-layer's resolved option, which its module's setup writes
|
||||
* into runtimeConfig.public.seo.pagination with its defaults (segments pl
|
||||
* strona, en page), so the routes match the links usePagination builds;
|
||||
* else the raw seo.pagination of the app's config. Read inside the
|
||||
* extendPages callback, which runs after every module's setup. No import
|
||||
* from vue-seo-layer: a git-installed layer's .ts is not type-stripped by
|
||||
* Node under node_modules.
|
||||
*/
|
||||
export function journalPaginationSource(nuxtOptions: unknown): unknown {
|
||||
if (nuxtOptions === null || typeof nuxtOptions !== 'object') return undefined
|
||||
const o = nuxtOptions as { runtimeConfig?: { public?: { seo?: { pagination?: unknown } } }; seo?: { pagination?: unknown } }
|
||||
const resolved = o.runtimeConfig?.public?.seo?.pagination
|
||||
if (resolved !== null && typeof resolved === 'object') return resolved
|
||||
return o.seo?.pagination
|
||||
}
|
||||
|
||||
/**
|
||||
* The extra listing routes of the path pagination form (D-40): with the
|
||||
* vue-seo-layer build option seo.pagination.form 'path', each listing gets
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineNuxtModule, extendPages } from 'nuxt/kit'
|
||||
import { DEFAULT_JOURNAL_ROUTES, JOURNAL_ROUTE_NAMES, journalPagedRoutes, normalizeJournalRoutes } from '../app/utils/routes.ts'
|
||||
import { DEFAULT_JOURNAL_ROUTES, JOURNAL_ROUTE_NAMES, journalPagedRoutes, journalPaginationSource, normalizeJournalRoutes } from '../app/utils/routes.ts'
|
||||
import type { JournalModuleOptions, JournalPostTitleTag, JournalRoutes } from '../app/utils/types.ts'
|
||||
|
||||
const PAGE_FILES: Record<keyof JournalRoutes, string> = {
|
||||
@@ -55,12 +55,13 @@ export default defineNuxtModule<JournalModuleOptions>({
|
||||
setup(options, nuxt) {
|
||||
const routes = normalizeJournalRoutes(options.routes as unknown as Record<string, unknown>)
|
||||
|
||||
// The path pagination form (vue-seo-layer's seo.pagination.form 'path',
|
||||
// D-40) adds <listing>/<segment>/:page routes rendering the same pages,
|
||||
// which read the page from the :page param.
|
||||
const paged = journalPagedRoutes(routes, (nuxt.options as { seo?: { pagination?: unknown } }).seo?.pagination)
|
||||
|
||||
extendPages((pages) => {
|
||||
// The path pagination form (vue-seo-layer's seo.pagination.form 'path',
|
||||
// D-40) adds <listing>/<segment>/:page routes rendering the same pages,
|
||||
// which read the page from the :page param. The segments come from the
|
||||
// SEO module's resolved option (its defaults included), written in its
|
||||
// setup, which has run by now (L4 WR-03).
|
||||
const paged = journalPagedRoutes(routes, journalPaginationSource(nuxt.options))
|
||||
for (const key of Object.keys(PAGE_FILES) as Array<keyof JournalRoutes>) {
|
||||
const path = routes[key]
|
||||
if (path === false) continue
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
JOURNAL_ROUTE_NAMES,
|
||||
fillJournalPath,
|
||||
journalPagedRoutes,
|
||||
journalPaginationSource,
|
||||
normalizeJournalRoutes,
|
||||
} from '../app/utils/routes.ts'
|
||||
|
||||
@@ -144,3 +145,19 @@ test('journalPagedRoutes: the query form, no option and disabled views add nothi
|
||||
path: '/news/p/:page(\\d+)',
|
||||
})
|
||||
})
|
||||
|
||||
test('journalPaginationSource: the SEO module\'s resolved option, else the raw seo.pagination (L4 WR-03)', () => {
|
||||
const resolved = { form: 'path', param: 'page', segments: { pl: 'strona', en: 'page' } }
|
||||
assert.deepEqual(journalPaginationSource({ runtimeConfig: { public: { seo: { pagination: resolved } } }, seo: { pagination: { form: 'path' } } }), resolved)
|
||||
assert.deepEqual(journalPaginationSource({ runtimeConfig: { public: {} }, seo: { pagination: { form: 'path' } } }), { form: 'path' })
|
||||
assert.deepEqual(journalPaginationSource({ runtimeConfig: { public: { seo: { cms: true } } }, seo: { pagination: { form: 'query' } } }), { form: 'query' })
|
||||
assert.equal(journalPaginationSource({}), undefined)
|
||||
assert.equal(journalPaginationSource(null), undefined)
|
||||
})
|
||||
|
||||
test('journalPagedRoutes: an app that set only form path gets the default pl and en segments through the resolved option', () => {
|
||||
// vue-seo-layer's setup writes runtimeConfig.public.seo.pagination with its defaults
|
||||
const nuxtOptions = { seo: { pagination: { form: 'path' } }, runtimeConfig: { public: { seo: { pagination: { form: 'path', param: 'page', segments: { pl: 'strona', en: 'page' } } } } } }
|
||||
const paths = journalPagedRoutes(normalizeJournalRoutes({ category: false, tag: false, author: false }), journalPaginationSource(nuxtOptions)).map((r) => r.path)
|
||||
assert.deepEqual(paths, ['/blog/strona/:page(\\d+)', '/blog/page/:page(\\d+)'])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user