wpheadless-lib 1.1.9 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/categories.d.ts +1 -1
- package/dist/api/categories.js +18 -2
- package/dist/api/index.d.ts +7 -6
- package/dist/api/index.js +7 -6
- package/dist/api/mappers.d.ts +1 -1
- package/dist/api/mappers.js +1 -1
- package/dist/api/menus.d.ts +18 -0
- package/dist/api/menus.js +39 -0
- package/dist/api/posts.js +30 -2
- package/dist/api/translations.js +7 -1
- package/dist/api/wpClient.d.ts +5 -8
- package/dist/api/wpClient.js +14 -0
- package/dist/app/config.d.ts +2 -1
- package/dist/app/config.js +4 -0
- package/dist/app/globals.css +33 -121
- package/dist/base/category/index.d.ts +3 -3
- package/dist/base/category/index.js +9 -8
- package/dist/base/category/pagination.d.ts +2 -2
- package/dist/base/category/pagination.js +12 -10
- package/dist/base/home/index.js +1 -1
- package/dist/base/home/pagination.js +3 -3
- package/dist/base/index.d.ts +7 -7
- package/dist/base/index.js +7 -7
- package/dist/base/layout/RootLayoutBase.d.ts +6 -7
- package/dist/base/layout/RootLayoutBase.js +4 -4
- package/dist/base/layout/RootLayoutBase.module.css +10 -0
- package/dist/base/legal/index.d.ts +4 -4
- package/dist/base/legal/index.js +36 -24
- package/dist/base/post/index.d.ts +1 -1
- package/dist/base/post/index.js +15 -12
- package/dist/components/Breadcrumbs/index.js +4 -4
- package/dist/components/JsonLd.d.ts +5 -0
- package/dist/components/JsonLd.js +9 -0
- package/dist/components/Paginator/index.d.ts +1 -1
- package/dist/components/Paginator/index.js +5 -5
- package/dist/components/PostCard/index.d.ts +3 -2
- package/dist/components/PostCard/index.js +8 -6
- package/dist/components/PostCard/index.module.css +7 -7
- package/dist/components/PostCard/index.types.d.ts +2 -1
- package/dist/components/PostCard/index.utils.d.ts +5 -5
- package/dist/components/PostCard/index.utils.js +8 -6
- package/dist/components/index.d.ts +6 -5
- package/dist/components/index.js +6 -5
- package/dist/components/layout/Footer/index.d.ts +5 -5
- package/dist/components/layout/Footer/index.js +11 -22
- package/dist/components/layout/Footer/index.module.css +59 -9
- package/dist/components/layout/Header/HeaderClient.d.ts +1 -1
- package/dist/components/layout/Header/HeaderClient.js +22 -2
- package/dist/components/layout/Header/index.d.ts +5 -5
- package/dist/components/layout/Header/index.js +10 -29
- package/dist/components/layout/Header/index.module.css +77 -16
- package/dist/components/ui/alert.d.ts +8 -0
- package/dist/components/ui/alert.js +12 -0
- package/dist/components/ui/alert.module.css +26 -0
- package/dist/components/ui/badge.d.ts +3 -0
- package/dist/components/ui/badge.js +9 -0
- package/dist/components/ui/badge.module.css +23 -0
- package/dist/components/ui/breadcrumb.d.ts +8 -0
- package/dist/components/ui/breadcrumb.js +22 -0
- package/dist/components/ui/breadcrumb.module.css +37 -0
- package/dist/components/ui/button.d.ts +7 -0
- package/dist/components/ui/button.js +16 -0
- package/dist/components/ui/button.module.css +72 -0
- package/dist/components/ui/card.d.ts +11 -0
- package/dist/components/ui/card.js +16 -0
- package/dist/components/ui/card.module.css +37 -0
- package/dist/components/ui/cn.d.ts +1 -0
- package/dist/components/ui/cn.js +3 -0
- package/dist/components/ui/index.d.ts +6 -0
- package/dist/components/ui/index.js +6 -0
- package/dist/components/ui/pagination.d.ts +14 -0
- package/dist/components/ui/pagination.js +25 -0
- package/dist/components/ui/pagination.module.css +62 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -6
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/plugins/yoast/index.js +10 -7
- package/dist/utils/hreflang.d.ts +4 -7
- package/dist/utils/hreflang.js +11 -6
- package/dist/utils/html.d.ts +2 -0
- package/dist/utils/html.js +25 -0
- package/dist/utils/index.d.ts +7 -6
- package/dist/utils/index.js +7 -6
- package/dist/utils/language.d.ts +37 -0
- package/dist/utils/language.js +75 -0
- package/dist/utils/seo.d.ts +1 -6
- package/dist/utils/seo.js +76 -12
- package/dist/utils/sitemap.d.ts +1 -1
- package/dist/utils/sitemap.js +107 -56
- package/dist/views/CategoryListView/index.d.ts +1 -1
- package/dist/views/CategoryListView/index.js +6 -6
- package/dist/views/CategoryListView/index.utils.d.ts +1 -1
- package/dist/views/CategoryListView/index.utils.js +1 -1
- package/dist/views/CategoryPaginationView/index.d.ts +1 -1
- package/dist/views/CategoryPaginationView/index.js +6 -6
- package/dist/views/CategoryPaginationView/index.utils.d.ts +1 -1
- package/dist/views/CategoryPaginationView/index.utils.js +1 -1
- package/dist/views/HomePaginationView/index.d.ts +1 -1
- package/dist/views/HomePaginationView/index.js +5 -4
- package/dist/views/HomePaginationView/index.utils.d.ts +1 -1
- package/dist/views/HomePaginationView/index.utils.js +1 -1
- package/dist/views/HomeView/index.d.ts +1 -1
- package/dist/views/HomeView/index.js +13 -12
- package/dist/views/HomeView/index.module.css +51 -8
- package/dist/views/HomeView/index.utils.d.ts +1 -1
- package/dist/views/HomeView/index.utils.js +2 -2
- package/dist/views/LegalPageView/index.d.ts +2 -1
- package/dist/views/LegalPageView/index.js +5 -5
- package/dist/views/LegalPageView/index.module.css +3 -3
- package/dist/views/LegalPageView/index.utils.js +1 -1
- package/dist/views/PostView/index.d.ts +3 -2
- package/dist/views/PostView/index.js +9 -7
- package/dist/views/PostView/index.module.css +46 -13
- package/dist/views/PostView/index.utils.d.ts +3 -3
- package/dist/views/PostView/index.utils.js +2 -2
- package/dist/views/index.d.ts +6 -6
- package/dist/views/index.js +6 -6
- package/package.json +47 -10
package/dist/utils/seo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stripTags } from "./html";
|
|
1
|
+
import { stripTags, toPlainText } from "./html.js";
|
|
2
2
|
export function getOgImageUrl(entity, featuredMedia) {
|
|
3
3
|
const ogUrl = entity?.yoast_head_json?.og_image?.[0]?.url;
|
|
4
4
|
return ogUrl || featuredMedia?.source_url;
|
|
@@ -19,6 +19,64 @@ const normalizeWpApiOrigin = (wpApiUrl) => {
|
|
|
19
19
|
const cleaned = wpApiUrl.replace(/\/wp-json\/?$/, "");
|
|
20
20
|
return getOrigin(cleaned);
|
|
21
21
|
};
|
|
22
|
+
const resolveRewriteOrigins = (opts, canonical) => {
|
|
23
|
+
const targetOrigin = getOrigin(opts.siteUrl);
|
|
24
|
+
const sourceOrigin = normalizeWpApiOrigin(opts.wpApiUrl) || getOrigin(canonical);
|
|
25
|
+
if (!targetOrigin || !sourceOrigin || targetOrigin === sourceOrigin) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return { targetOrigin, sourceOrigin };
|
|
29
|
+
};
|
|
30
|
+
const IMAGE_EXTENSIONS = [
|
|
31
|
+
".jpg",
|
|
32
|
+
".jpeg",
|
|
33
|
+
".png",
|
|
34
|
+
".webp",
|
|
35
|
+
".gif",
|
|
36
|
+
".svg",
|
|
37
|
+
".avif",
|
|
38
|
+
".bmp",
|
|
39
|
+
".tiff",
|
|
40
|
+
];
|
|
41
|
+
const isImageUrl = (url) => {
|
|
42
|
+
const pathname = url.pathname.toLowerCase();
|
|
43
|
+
return (pathname.includes("/wp-content/uploads/") ||
|
|
44
|
+
IMAGE_EXTENSIONS.some((ext) => pathname.endsWith(ext)));
|
|
45
|
+
};
|
|
46
|
+
const rewriteSchemaValue = (value, { targetOrigin, sourceOrigin }) => {
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return value.map((item) => rewriteSchemaValue(item, { targetOrigin, sourceOrigin }));
|
|
49
|
+
}
|
|
50
|
+
if (value && typeof value === "object") {
|
|
51
|
+
const obj = value;
|
|
52
|
+
const rewritten = {};
|
|
53
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
54
|
+
rewritten[key] = rewriteSchemaValue(val, { targetOrigin, sourceOrigin });
|
|
55
|
+
}
|
|
56
|
+
return rewritten;
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === "string") {
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = new URL(value);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
if (parsed.origin !== sourceOrigin)
|
|
67
|
+
return value;
|
|
68
|
+
if (isImageUrl(parsed))
|
|
69
|
+
return value;
|
|
70
|
+
return value.split(sourceOrigin).join(targetOrigin);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
};
|
|
74
|
+
const rewriteYoastSchemaUrls = (schema, opts, canonical) => {
|
|
75
|
+
const origins = resolveRewriteOrigins(opts, canonical);
|
|
76
|
+
if (!origins)
|
|
77
|
+
return schema;
|
|
78
|
+
return rewriteSchemaValue(schema, origins);
|
|
79
|
+
};
|
|
22
80
|
/**
|
|
23
81
|
* Replace Yoast URLs so they match the frontend domain instead of the WP API domain.
|
|
24
82
|
* It stringifies the object to replace origins and keeps structure intact.
|
|
@@ -26,14 +84,14 @@ const normalizeWpApiOrigin = (wpApiUrl) => {
|
|
|
26
84
|
export function rewriteYoastUrls(yoast, opts = {}) {
|
|
27
85
|
if (!yoast)
|
|
28
86
|
return yoast;
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
if (!targetOrigin || !sourceOrigin || targetOrigin === sourceOrigin) {
|
|
87
|
+
const origins = resolveRewriteOrigins(opts, yoast.canonical);
|
|
88
|
+
if (!origins)
|
|
32
89
|
return yoast;
|
|
33
|
-
}
|
|
34
90
|
try {
|
|
35
91
|
const serialized = JSON.stringify(yoast);
|
|
36
|
-
const replaced = serialized
|
|
92
|
+
const replaced = serialized
|
|
93
|
+
.split(origins.sourceOrigin)
|
|
94
|
+
.join(origins.targetOrigin);
|
|
37
95
|
return JSON.parse(replaced);
|
|
38
96
|
}
|
|
39
97
|
catch {
|
|
@@ -112,8 +170,10 @@ export function isYoastNoindex(yoast) {
|
|
|
112
170
|
return robots.index === false;
|
|
113
171
|
}
|
|
114
172
|
export function getYoastSchema(entity, opts) {
|
|
115
|
-
const yoast =
|
|
116
|
-
|
|
173
|
+
const yoast = entity?.yoast_head_json;
|
|
174
|
+
if (!yoast?.schema)
|
|
175
|
+
return null;
|
|
176
|
+
return rewriteYoastSchemaUrls(yoast.schema, opts ?? {}, yoast.canonical) || null;
|
|
117
177
|
}
|
|
118
178
|
export function stripBreadcrumbsFromSchema(schema) {
|
|
119
179
|
if (!schema || typeof schema !== "object")
|
|
@@ -136,10 +196,14 @@ export function getFeaturedMedia(entity) {
|
|
|
136
196
|
return entity?._embedded?.["wp:featuredmedia"]?.[0];
|
|
137
197
|
}
|
|
138
198
|
export function getPlainTextExcerpt(html, maxLength = 160) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
199
|
+
const text = toPlainText(html);
|
|
200
|
+
if (!text || maxLength <= 0 || text.length <= maxLength)
|
|
201
|
+
return text;
|
|
202
|
+
const truncated = text.slice(0, maxLength).trimEnd();
|
|
203
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
204
|
+
return lastSpace > maxLength * 0.6
|
|
205
|
+
? truncated.slice(0, lastSpace).trimEnd()
|
|
206
|
+
: truncated;
|
|
143
207
|
}
|
|
144
208
|
export function buildBreadcrumbSchema(items) {
|
|
145
209
|
return {
|
package/dist/utils/sitemap.d.ts
CHANGED
package/dist/utils/sitemap.js
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
1
|
import { createPageEndpoints, createPostsEndpoints } from "wpjsapi-lib";
|
|
2
|
-
import { createWpClient } from "../api/wpClient";
|
|
3
|
-
import { listPostsWithEmbeds } from "../api/posts";
|
|
4
|
-
import { getCategoryPaginationStaticParams } from "../base";
|
|
5
|
-
import { getHomePaginationStaticParams } from "../base/home/pagination";
|
|
6
|
-
import { getAbsoluteUrl, ensureTrailingSlash } from "./routing";
|
|
7
|
-
import { isYoastNoindex } from "./seo";
|
|
8
|
-
import { getTranslationKey, getEntityLanguageId, withXDefault, } from "./hreflang";
|
|
2
|
+
import { createWpClient, isValidWpApiUrl } from "../api/wpClient.js";
|
|
3
|
+
import { listPostsWithEmbeds } from "../api/posts.js";
|
|
4
|
+
import { getCategoryPaginationStaticParams } from "../base/index.js";
|
|
5
|
+
import { getHomePaginationStaticParams } from "../base/home/pagination.js";
|
|
6
|
+
import { getAbsoluteUrl, ensureTrailingSlash } from "./routing.js";
|
|
7
|
+
import { isYoastNoindex } from "./seo.js";
|
|
8
|
+
import { getTranslationKey, getEntityLanguageId, withXDefault, } from "./hreflang.js";
|
|
9
|
+
import { normalizeLanguages, shouldRenderLanguageAlternates, } from "./language.js";
|
|
9
10
|
const resolveWpApiUrl = (cfg) => cfg.wpApiUrl ?? process.env.WP_API_URL ?? "";
|
|
10
11
|
const resolveSiteUrl = (cfg) => cfg.siteUrl ?? process.env.NEXT_PUBLIC_SITE_URL;
|
|
11
|
-
const buildHomeAlternates = (cfg) =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const buildHomeAlternates = (cfg) => shouldRenderLanguageAlternates(cfg.languages)
|
|
13
|
+
? withXDefault(normalizeLanguages(cfg.languages).reduce((acc, langCfg) => {
|
|
14
|
+
const basePath = ensureTrailingSlash(langCfg.basePath || "/");
|
|
15
|
+
acc[langCfg.code] = getAbsoluteUrl(basePath, resolveSiteUrl(cfg));
|
|
16
|
+
return acc;
|
|
17
|
+
}, {}))
|
|
18
|
+
: undefined;
|
|
19
|
+
const escapeXml = (value) => value
|
|
20
|
+
.replace(/&/g, "&")
|
|
21
|
+
.replace(/</g, "<")
|
|
22
|
+
.replace(/>/g, ">")
|
|
23
|
+
.replace(/"/g, """)
|
|
24
|
+
.replace(/'/g, "'");
|
|
25
|
+
const renderAlternates = (alternates) => {
|
|
26
|
+
const normalizedAlternates = withXDefault(alternates);
|
|
27
|
+
if (!normalizedAlternates)
|
|
28
|
+
return undefined;
|
|
29
|
+
return Object.entries(normalizedAlternates)
|
|
30
|
+
.map(([code, href]) => `<xhtml:link rel="alternate" hreflang="${escapeXml(code)}" href="${escapeXml(href)}" />`)
|
|
31
|
+
.join("");
|
|
32
|
+
};
|
|
16
33
|
const getYoastHeadJson = (entity) => entity.yoast_head_json;
|
|
17
34
|
const isNoindexEntity = (entity) => isYoastNoindex(getYoastHeadJson(entity));
|
|
35
|
+
const getWpLangParam = (language) => {
|
|
36
|
+
const lang = language.wpLang || (language.langId ? language.code : undefined);
|
|
37
|
+
return lang ? { lang } : {};
|
|
38
|
+
};
|
|
39
|
+
async function mapConcurrent(items, mapper, concurrency = 5) {
|
|
40
|
+
const results = new Array(items.length);
|
|
41
|
+
let nextIndex = 0;
|
|
42
|
+
async function worker() {
|
|
43
|
+
while (nextIndex < items.length) {
|
|
44
|
+
const currentIndex = nextIndex;
|
|
45
|
+
nextIndex += 1;
|
|
46
|
+
results[currentIndex] = await mapper(items[currentIndex], currentIndex);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
18
52
|
export function renderSitemap(entries) {
|
|
19
53
|
const items = entries
|
|
20
54
|
.map((entry) => {
|
|
21
|
-
const links = entry.alternates
|
|
22
|
-
|
|
23
|
-
.map(([code, href]) => `<xhtml:link rel="alternate" hreflang="${code}" href="${href}" />`)
|
|
24
|
-
.join("");
|
|
25
|
-
return `<url><loc>${entry.url}</loc>${entry.lastmod ? `<lastmod>${entry.lastmod}</lastmod>` : ""}${links ?? ""}</url>`;
|
|
55
|
+
const links = renderAlternates(entry.alternates);
|
|
56
|
+
return `<url><loc>${escapeXml(entry.url)}</loc>${entry.lastmod ? `<lastmod>${escapeXml(entry.lastmod)}</lastmod>` : ""}${links ?? ""}</url>`;
|
|
26
57
|
})
|
|
27
58
|
.join("");
|
|
28
59
|
return (`<?xml version="1.0" encoding="UTF-8"?>` +
|
|
@@ -32,7 +63,7 @@ export function renderSitemap(entries) {
|
|
|
32
63
|
}
|
|
33
64
|
export function renderSitemapIndex(sitemaps) {
|
|
34
65
|
const items = sitemaps
|
|
35
|
-
.map((s) => `<sitemap><loc>${s.url}</loc>${s.lastmod ? `<lastmod>${s.lastmod}</lastmod>` : ""}</sitemap>`)
|
|
66
|
+
.map((s) => `<sitemap><loc>${escapeXml(s.url)}</loc>${s.lastmod ? `<lastmod>${escapeXml(s.lastmod)}</lastmod>` : ""}</sitemap>`)
|
|
36
67
|
.join("");
|
|
37
68
|
return (`<?xml version="1.0" encoding="UTF-8"?>` +
|
|
38
69
|
`<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
|
@@ -40,38 +71,42 @@ export function renderSitemapIndex(sitemaps) {
|
|
|
40
71
|
`</sitemapindex>`);
|
|
41
72
|
}
|
|
42
73
|
export async function getHomeEntries(cfg) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
74
|
+
const { runtimeConfig } = cfg;
|
|
75
|
+
const languages = normalizeLanguages(cfg.languages);
|
|
45
76
|
const wpApiUrl = resolveWpApiUrl(cfg);
|
|
46
77
|
const siteUrl = resolveSiteUrl(cfg);
|
|
47
78
|
const homeAlternates = buildHomeAlternates(cfg);
|
|
48
|
-
|
|
79
|
+
return mapConcurrent(languages, async (langCfg) => {
|
|
49
80
|
const basePath = ensureTrailingSlash(langCfg.basePath || "/");
|
|
50
81
|
const homeUrl = getAbsoluteUrl(basePath, siteUrl);
|
|
51
|
-
const homePosts =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
const homePosts = isValidWpApiUrl(wpApiUrl)
|
|
83
|
+
? await listPostsWithEmbeds({
|
|
84
|
+
baseApiUrl: wpApiUrl,
|
|
85
|
+
perPage: runtimeConfig.postsPerPagePagination,
|
|
86
|
+
page: 1,
|
|
87
|
+
langId: langCfg.langId,
|
|
88
|
+
orderby: "date",
|
|
89
|
+
order: "desc",
|
|
90
|
+
})
|
|
91
|
+
: null;
|
|
92
|
+
const latest = homePosts?.ok ? homePosts.data?.items[0] : undefined;
|
|
60
93
|
const lastmod = latest?.modified ||
|
|
61
94
|
latest?.date;
|
|
62
|
-
|
|
95
|
+
return {
|
|
63
96
|
url: homeUrl,
|
|
64
97
|
lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
|
|
65
98
|
alternates: homeAlternates ?? undefined,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return entries;
|
|
99
|
+
};
|
|
100
|
+
});
|
|
69
101
|
}
|
|
70
102
|
export async function getHomePaginationEntries(cfg) {
|
|
71
103
|
const entries = [];
|
|
72
|
-
const {
|
|
104
|
+
const { runtimeConfig } = cfg;
|
|
105
|
+
const languages = normalizeLanguages(cfg.languages);
|
|
73
106
|
const wpApiUrl = resolveWpApiUrl(cfg);
|
|
74
107
|
const siteUrl = resolveSiteUrl(cfg);
|
|
108
|
+
if (!isValidWpApiUrl(wpApiUrl))
|
|
109
|
+
return entries;
|
|
75
110
|
for (const langCfg of languages) {
|
|
76
111
|
const basePath = ensureTrailingSlash(langCfg.basePath || "/");
|
|
77
112
|
const pages = await getHomePaginationStaticParams({
|
|
@@ -79,7 +114,7 @@ export async function getHomePaginationEntries(cfg) {
|
|
|
79
114
|
perPage: runtimeConfig.postsPerPagePagination,
|
|
80
115
|
langId: langCfg.langId,
|
|
81
116
|
});
|
|
82
|
-
|
|
117
|
+
const pageEntries = await mapConcurrent(pages, async (p) => {
|
|
83
118
|
const url = getAbsoluteUrl(`${basePath}page/${p.page}/`, siteUrl);
|
|
84
119
|
const pagePosts = await listPostsWithEmbeds({
|
|
85
120
|
baseApiUrl: wpApiUrl,
|
|
@@ -92,25 +127,29 @@ export async function getHomePaginationEntries(cfg) {
|
|
|
92
127
|
const latest = pagePosts.ok ? pagePosts.data?.items[0] : undefined;
|
|
93
128
|
const lastmod = latest?.modified ||
|
|
94
129
|
latest?.date;
|
|
95
|
-
|
|
130
|
+
return {
|
|
96
131
|
url,
|
|
97
132
|
lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
|
|
98
|
-
}
|
|
99
|
-
}
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
entries.push(...pageEntries);
|
|
100
136
|
}
|
|
101
137
|
return entries;
|
|
102
138
|
}
|
|
103
139
|
export async function getCategoryEntries(cfg) {
|
|
104
140
|
const entries = [];
|
|
105
|
-
const {
|
|
141
|
+
const { runtimeConfig } = cfg;
|
|
142
|
+
const languages = normalizeLanguages(cfg.languages);
|
|
106
143
|
const wpApiUrl = resolveWpApiUrl(cfg);
|
|
107
144
|
const siteUrl = resolveSiteUrl(cfg);
|
|
145
|
+
if (!isValidWpApiUrl(wpApiUrl))
|
|
146
|
+
return entries;
|
|
108
147
|
const categoriesPerLang = {};
|
|
109
148
|
const categoryAlternates = {};
|
|
110
149
|
for (const langCfg of languages) {
|
|
111
150
|
const { categoriesApi } = createWpClient({ baseUrl: wpApiUrl });
|
|
112
151
|
const categories = await categoriesApi.listAll({
|
|
113
|
-
...(langCfg
|
|
152
|
+
...getWpLangParam(langCfg),
|
|
114
153
|
_fields: ["id", "slug", "name", "description", "meta", "yoast_head_json"],
|
|
115
154
|
});
|
|
116
155
|
const indexableCategories = categories.filter((cat) => !isNoindexEntity(cat));
|
|
@@ -130,7 +169,7 @@ export async function getCategoryEntries(cfg) {
|
|
|
130
169
|
for (const langCfg of languages) {
|
|
131
170
|
const basePath = ensureTrailingSlash(langCfg.basePath || "/");
|
|
132
171
|
const categories = categoriesPerLang[langCfg.code] ?? [];
|
|
133
|
-
|
|
172
|
+
const categoryEntries = await mapConcurrent(categories, async (cat) => {
|
|
134
173
|
const url = getAbsoluteUrl(`${basePath}${cat.slug}/`, siteUrl);
|
|
135
174
|
const catPosts = await listPostsWithEmbeds({
|
|
136
175
|
baseApiUrl: wpApiUrl,
|
|
@@ -151,25 +190,29 @@ export async function getCategoryEntries(cfg) {
|
|
|
151
190
|
const a = withXDefault(categoryAlternates[key]);
|
|
152
191
|
return a;
|
|
153
192
|
})();
|
|
154
|
-
|
|
193
|
+
return {
|
|
155
194
|
url,
|
|
156
195
|
lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
|
|
157
196
|
alternates: alt,
|
|
158
|
-
}
|
|
159
|
-
}
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
entries.push(...categoryEntries);
|
|
160
200
|
}
|
|
161
201
|
return entries;
|
|
162
202
|
}
|
|
163
203
|
export async function getCategoryPaginationEntries(cfg) {
|
|
164
204
|
const entries = [];
|
|
165
|
-
const {
|
|
205
|
+
const { runtimeConfig } = cfg;
|
|
206
|
+
const languages = normalizeLanguages(cfg.languages);
|
|
166
207
|
const wpApiUrl = resolveWpApiUrl(cfg);
|
|
167
208
|
const siteUrl = resolveSiteUrl(cfg);
|
|
209
|
+
if (!isValidWpApiUrl(wpApiUrl))
|
|
210
|
+
return entries;
|
|
168
211
|
const catIdsBySlugByLang = {};
|
|
169
212
|
for (const langCfg of languages) {
|
|
170
213
|
const { categoriesApi } = createWpClient({ baseUrl: wpApiUrl });
|
|
171
214
|
const categories = await categoriesApi.listAll({
|
|
172
|
-
|
|
215
|
+
...getWpLangParam(langCfg),
|
|
173
216
|
_fields: ["id", "slug", "yoast_head_json"],
|
|
174
217
|
});
|
|
175
218
|
const indexableCategories = categories.filter((cat) => !isNoindexEntity(cat));
|
|
@@ -179,15 +222,15 @@ export async function getCategoryPaginationEntries(cfg) {
|
|
|
179
222
|
const catPages = await getCategoryPaginationStaticParams({
|
|
180
223
|
baseApiUrl: wpApiUrl,
|
|
181
224
|
perPage: runtimeConfig.postsPerPagePagination,
|
|
182
|
-
lang: langCfg.code,
|
|
225
|
+
lang: langCfg.wpLang || (langCfg.langId ? langCfg.code : undefined) || "",
|
|
183
226
|
langId: langCfg.langId,
|
|
184
227
|
});
|
|
185
228
|
const basePath = ensureTrailingSlash(langCfg.basePath || "/");
|
|
186
|
-
|
|
229
|
+
const pageEntries = await mapConcurrent(catPages, async (page) => {
|
|
187
230
|
const url = getAbsoluteUrl(`${basePath}${page.category}/page/${page.page}/`, siteUrl);
|
|
188
231
|
const catId = catIdsBySlugByLang[langCfg.code]?.[page.category];
|
|
189
232
|
if (!catId)
|
|
190
|
-
|
|
233
|
+
return null;
|
|
191
234
|
let lastmod;
|
|
192
235
|
const pagePosts = await listPostsWithEmbeds({
|
|
193
236
|
baseApiUrl: wpApiUrl,
|
|
@@ -202,20 +245,23 @@ export async function getCategoryPaginationEntries(cfg) {
|
|
|
202
245
|
lastmod =
|
|
203
246
|
latest?.modified ||
|
|
204
247
|
latest?.date;
|
|
205
|
-
|
|
248
|
+
return {
|
|
206
249
|
url,
|
|
207
250
|
lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
|
|
208
|
-
}
|
|
209
|
-
}
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
entries.push(...pageEntries.filter(Boolean));
|
|
210
254
|
}
|
|
211
255
|
return entries;
|
|
212
256
|
}
|
|
213
257
|
export async function getPostEntriesForLang(langCode, cfg) {
|
|
214
|
-
const
|
|
258
|
+
const languages = normalizeLanguages(cfg.languages);
|
|
215
259
|
const langCfg = languages.find((l) => l.code === langCode);
|
|
216
260
|
if (!langCfg)
|
|
217
261
|
return [];
|
|
218
262
|
const wpApiUrl = resolveWpApiUrl(cfg);
|
|
263
|
+
if (!isValidWpApiUrl(wpApiUrl))
|
|
264
|
+
return [];
|
|
219
265
|
const siteUrl = resolveSiteUrl(cfg);
|
|
220
266
|
const postsApi = createPostsEndpoints({ baseUrl: wpApiUrl });
|
|
221
267
|
const languageFilter = langCfg.langId
|
|
@@ -228,7 +274,7 @@ export async function getPostEntriesForLang(langCode, cfg) {
|
|
|
228
274
|
});
|
|
229
275
|
const indexablePosts = posts.filter((post) => !isNoindexEntity(post));
|
|
230
276
|
const postAlternates = {};
|
|
231
|
-
|
|
277
|
+
const postsByLanguage = await mapConcurrent(languages, async (l) => {
|
|
232
278
|
const base = ensureTrailingSlash(l.basePath || "/");
|
|
233
279
|
const langPosts = l.code === langCfg.code
|
|
234
280
|
? indexablePosts
|
|
@@ -240,6 +286,9 @@ export async function getPostEntriesForLang(langCode, cfg) {
|
|
|
240
286
|
const filteredLangPosts = l.code === langCfg.code
|
|
241
287
|
? langPosts
|
|
242
288
|
: langPosts.filter((post) => !isNoindexEntity(post));
|
|
289
|
+
return { language: l, base, posts: filteredLangPosts };
|
|
290
|
+
});
|
|
291
|
+
for (const { language: l, base, posts: filteredLangPosts } of postsByLanguage) {
|
|
243
292
|
for (const p of filteredLangPosts) {
|
|
244
293
|
const translationKey = getTranslationKey(p);
|
|
245
294
|
if (!translationKey)
|
|
@@ -278,9 +327,11 @@ export async function getPostEntriesForLang(langCode, cfg) {
|
|
|
278
327
|
}
|
|
279
328
|
export async function getLegalEntries(cfg) {
|
|
280
329
|
const entries = [];
|
|
281
|
-
const
|
|
330
|
+
const languages = normalizeLanguages(cfg.languages);
|
|
282
331
|
const siteUrl = resolveSiteUrl(cfg);
|
|
283
332
|
const wpApiUrl = resolveWpApiUrl(cfg);
|
|
333
|
+
if (!isValidWpApiUrl(wpApiUrl))
|
|
334
|
+
return entries;
|
|
284
335
|
const pagesApi = createPageEndpoints({ baseUrl: wpApiUrl });
|
|
285
336
|
const pagesPerLang = {};
|
|
286
337
|
const alternatesByKey = {};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import Paginator from "../../components/Paginator";
|
|
3
|
-
import PostCard from "../../components/PostCard";
|
|
2
|
+
import Paginator from "../../components/Paginator/index.js";
|
|
3
|
+
import PostCard from "../../components/PostCard/index.js";
|
|
4
4
|
import styles from "./index.module.css";
|
|
5
|
-
import { getCountLabel, getEmptyLabel, getPaginatorBase } from "./index.utils";
|
|
6
|
-
import { Breadcrumbs } from "../../components";
|
|
7
|
-
import { getTranslator } from "../../utils";
|
|
5
|
+
import { getCountLabel, getEmptyLabel, getPaginatorBase } from "./index.utils.js";
|
|
6
|
+
import { Breadcrumbs, JsonLd } from "../../components/index.js";
|
|
7
|
+
import { getTranslator, getYoastSchema } from "../../utils/index.js";
|
|
8
8
|
export function CategoryListView({ category, posts, totalPages, baseUrl, homeHref = "/", linkBasePath, locale, dateLocale = "en-US", }) {
|
|
9
9
|
const paginatorBase = getPaginatorBase(category, baseUrl);
|
|
10
10
|
const countLabel = getCountLabel(category, locale);
|
|
11
11
|
const emptyLabel = getEmptyLabel(locale);
|
|
12
12
|
const t = getTranslator(locale);
|
|
13
|
-
return (_jsxs("div", { className:
|
|
13
|
+
return (_jsxs("div", { className: styles.container, children: [_jsx(JsonLd, { data: getYoastSchema(category) }), _jsxs("header", { className: styles.header, children: [_jsx(Breadcrumbs, { items: [
|
|
14
14
|
{ label: "Home", href: homeHref },
|
|
15
15
|
{ label: category.name },
|
|
16
16
|
] }), _jsx("h1", { className: styles.title, children: category.name }), category.description && (_jsx("div", { className: styles.description, dangerouslySetInnerHTML: { __html: category.description } })), countLabel && _jsx("p", { className: styles.count, children: countLabel })] }), posts.length === 0 ? (_jsx("p", { className: styles.empty, children: emptyLabel })) : (_jsxs("section", { "aria-label": t("home.title"), className: styles.gridSection, children: [_jsx("div", { className: styles.grid, children: posts.map((post) => (_jsx(PostCard, { post: post, categorySlug: category.slug, basePath: linkBasePath, locale: locale, dateLocale: dateLocale }, post.id))) }), _jsx(Paginator, { currentPage: 1, totalPages: totalPages, baseUrl: paginatorBase, locale: locale })] }))] }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type WPCategory } from "wpjsapi-lib";
|
|
2
|
-
import { type Locale } from "../../utils";
|
|
2
|
+
import { type Locale } from "../../utils/index.js";
|
|
3
3
|
export declare const getPaginatorBase: (category: WPCategory, baseUrl?: string) => string;
|
|
4
4
|
export declare const getCountLabel: (category: WPCategory, locale?: Locale) => string | null;
|
|
5
5
|
export declare const getEmptyLabel: (locale?: Locale) => string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getTranslator } from "../../utils";
|
|
1
|
+
import { getTranslator } from "../../utils/index.js";
|
|
2
2
|
export const getPaginatorBase = (category, baseUrl) => baseUrl ?? `/${category.slug}`;
|
|
3
3
|
export const getCountLabel = (category, locale) => {
|
|
4
4
|
const t = getTranslator(locale);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import Paginator from "../../components/Paginator";
|
|
3
|
-
import PostCard from "../../components/PostCard";
|
|
2
|
+
import Paginator from "../../components/Paginator/index.js";
|
|
3
|
+
import PostCard from "../../components/PostCard/index.js";
|
|
4
4
|
import styles from "./index.module.css";
|
|
5
|
-
import { getEmptyLabel, getPageLabel, getPaginatorBase } from "./index.utils";
|
|
6
|
-
import { Breadcrumbs } from "../../components";
|
|
7
|
-
import { getTranslator } from "../../utils";
|
|
5
|
+
import { getEmptyLabel, getPageLabel, getPaginatorBase } from "./index.utils.js";
|
|
6
|
+
import { Breadcrumbs } from "../../components/index.js";
|
|
7
|
+
import { getTranslator } from "../../utils/index.js";
|
|
8
8
|
export function CategoryPaginationView({ category, posts, currentPage, totalPages, baseUrl, linkBasePath, locale, dateLocale = "en-US", }) {
|
|
9
9
|
const paginatorBase = getPaginatorBase(category, baseUrl);
|
|
10
10
|
const pageLabel = getPageLabel(locale, currentPage);
|
|
11
11
|
const emptyLabel = getEmptyLabel(locale);
|
|
12
12
|
const t = getTranslator(locale);
|
|
13
|
-
return (_jsxs("div", { className:
|
|
13
|
+
return (_jsxs("div", { className: styles.container, children: [_jsxs("header", { className: styles.header, children: [_jsx(Breadcrumbs, { items: [
|
|
14
14
|
{ label: "Home", href: paginatorBase || "/" },
|
|
15
15
|
{ label: category.name },
|
|
16
16
|
{ label: pageLabel },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type WPCategory } from "wpjsapi-lib";
|
|
2
|
-
import { type Locale } from "../../utils";
|
|
2
|
+
import { type Locale } from "../../utils/index.js";
|
|
3
3
|
export declare const getPaginatorBase: (category: WPCategory, baseUrl?: string) => string;
|
|
4
4
|
export declare const getPageLabel: (locale: Locale | undefined, page: number) => string;
|
|
5
5
|
export declare const getEmptyLabel: (locale?: Locale) => string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getTranslator } from "../../utils";
|
|
1
|
+
import { getTranslator } from "../../utils/index.js";
|
|
2
2
|
export const getPaginatorBase = (category, baseUrl) => baseUrl ?? `/${category.slug}`;
|
|
3
3
|
export const getPageLabel = (locale, page) => getTranslator(locale)("category.pageLabel", { page });
|
|
4
4
|
export const getEmptyLabel = (locale) => getTranslator(locale)("category.emptyPage");
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import Paginator from "../../components/Paginator";
|
|
3
|
-
import PostCard from "../../components/PostCard";
|
|
2
|
+
import Paginator from "../../components/Paginator/index.js";
|
|
3
|
+
import PostCard from "../../components/PostCard/index.js";
|
|
4
4
|
import styles from "./index.module.css";
|
|
5
|
-
import { buildHomePaginationCopy } from "./index.utils";
|
|
5
|
+
import { buildHomePaginationCopy } from "./index.utils.js";
|
|
6
|
+
import { Alert, AlertDescription } from "../../components/ui/index.js";
|
|
6
7
|
export function HomePaginationView({ posts, currentPage, totalPages, error, baseUrl = "", linkBasePath, locale, dateLocale = "en-US", }) {
|
|
7
8
|
const copy = buildHomePaginationCopy(locale, currentPage, error);
|
|
8
|
-
return (_jsxs("div", { className:
|
|
9
|
+
return (_jsxs("div", { className: styles.container, children: [_jsxs("header", { className: styles.header, children: [_jsx("h1", { className: styles.title, children: copy.title }), _jsx("p", { className: styles.subtitle, children: copy.subtitle })] }), error ? (_jsx(Alert, { className: styles.errorCard, variant: "destructive", children: copy.errorText && (_jsx(AlertDescription, { children: copy.errorText })) })) : posts.length === 0 ? (_jsx("p", { className: styles.empty, children: copy.empty })) : (_jsxs("section", { "aria-label": copy.title, className: styles.gridSection, role: "region", children: [_jsx("div", { className: styles.grid, children: posts.map((post) => (_jsx(PostCard, { post: post, basePath: linkBasePath, locale: locale, dateLocale: dateLocale }, post.id))) }), _jsx(Paginator, { currentPage: currentPage, totalPages: totalPages, baseUrl: baseUrl, locale: locale })] }))] }));
|
|
9
10
|
}
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import Image from "next/image";
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import Paginator from "../../components/Paginator";
|
|
5
|
-
import PostCard from "../../components/PostCard";
|
|
4
|
+
import Paginator from "../../components/Paginator/index.js";
|
|
5
|
+
import PostCard from "../../components/PostCard/index.js";
|
|
6
6
|
import styles from "./index.module.css";
|
|
7
|
-
import { buildFeaturedViewModel, getHomeCopy } from "./index.utils";
|
|
7
|
+
import { buildFeaturedViewModel, getHomeCopy } from "./index.utils.js";
|
|
8
|
+
import { Alert, AlertDescription, AlertTitle, badgeClassName, } from "../../components/ui/index.js";
|
|
8
9
|
function truncateLabel(label, max = 20) {
|
|
9
10
|
const trimmed = label.trim();
|
|
10
|
-
return trimmed.length > max ? `${trimmed.slice(0, max)}
|
|
11
|
+
return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
|
|
11
12
|
}
|
|
12
13
|
export function HomeView({ featuredPost, regularPosts, error, totalPages, baseUrl = "", linkBasePath, locale, dateLocale = "en-US", }) {
|
|
13
14
|
const copy = getHomeCopy(locale);
|
|
14
15
|
const featuredModel = featuredPost
|
|
15
16
|
? buildFeaturedViewModel(featuredPost, locale, dateLocale, linkBasePath)
|
|
16
17
|
: null;
|
|
17
|
-
return (_jsx("div", { className:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
return (_jsx("div", { className: styles.container, children: error ? (_jsxs(Alert, { className: styles.errorBox, variant: "destructive", children: [_jsxs(AlertTitle, { children: [copy.errorTitle, ": ", error] }), _jsx(AlertDescription, { className: styles.errorHint, children: copy.errorHint })] })) : !featuredModel && regularPosts.length === 0 ? (_jsx("p", { className: styles.empty, children: copy.empty })) : (_jsxs(_Fragment, { children: [_jsx("h1", { id: "home-title", className: styles.visuallyHidden, children: copy.title }), _jsx("section", { className: styles.masthead, "aria-labelledby": "home-title", children: featuredModel && (_jsxs("article", { className: `${styles.featured} ${featuredModel.image
|
|
19
|
+
? styles.featuredWithImage
|
|
20
|
+
: styles.featuredNoImage}`, children: [featuredModel.image && (_jsxs("div", { className: styles.featuredImageFrame, children: [_jsx(Image, { src: featuredModel.image.src, alt: featuredModel.image.alt, fill: true, className: styles.featuredImage, priority: true, sizes: "(max-width: 768px) 100vw, 50vw" }), _jsx("div", { className: styles.featuredOverlay, "aria-hidden": "true" })] })), _jsxs("div", { className: styles.featuredBody, children: [featuredModel.badgeLabel &&
|
|
21
|
+
(featuredModel.badgeUrl ? (_jsx(Link, { href: featuredModel.badgeUrl, className: badgeClassName(styles.featuredBadge), title: featuredModel.badgeLabel, children: truncateLabel(featuredModel.badgeLabel) })) : (_jsx("span", { className: badgeClassName(styles.featuredBadge), title: featuredModel.badgeLabel, children: truncateLabel(featuredModel.badgeLabel) }))), _jsx(Link, { href: featuredModel.postUrl, children: _jsx("h2", { dangerouslySetInnerHTML: {
|
|
22
|
+
__html: featuredModel.titleHtml,
|
|
23
|
+
}, className: styles.featuredTitle }) }), _jsx("div", { dangerouslySetInnerHTML: {
|
|
24
|
+
__html: featuredModel.excerptHtml,
|
|
25
|
+
}, className: `text-muted ${styles.featuredExcerpt}` }), _jsx("time", { dateTime: featuredModel.dateTime, className: `text-muted ${styles.featuredDate}`, children: featuredModel.dateLabel })] })] })) }), _jsx("section", { className: styles.postsGrid, "aria-label": copy.title, role: "region", children: regularPosts.map((post) => (_jsx(PostCard, { post: post, basePath: linkBasePath, locale: locale, dateLocale: dateLocale }, post.id))) }), _jsx(Paginator, { currentPage: 1, totalPages: totalPages, baseUrl: baseUrl, locale: locale })] })) }));
|
|
25
26
|
}
|