wpheadless-lib 1.1.10 → 1.1.12

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.
Files changed (120) hide show
  1. package/dist/api/categories.d.ts +1 -1
  2. package/dist/api/categories.js +18 -2
  3. package/dist/api/index.d.ts +7 -6
  4. package/dist/api/index.js +7 -6
  5. package/dist/api/mappers.d.ts +1 -1
  6. package/dist/api/mappers.js +1 -1
  7. package/dist/api/menus.d.ts +18 -0
  8. package/dist/api/menus.js +39 -0
  9. package/dist/api/posts.js +30 -2
  10. package/dist/api/translations.js +7 -1
  11. package/dist/api/wpClient.d.ts +5 -8
  12. package/dist/api/wpClient.js +14 -0
  13. package/dist/app/config.d.ts +2 -1
  14. package/dist/app/config.js +4 -0
  15. package/dist/app/globals.css +35 -124
  16. package/dist/base/category/index.d.ts +3 -3
  17. package/dist/base/category/index.js +9 -8
  18. package/dist/base/category/pagination.d.ts +2 -2
  19. package/dist/base/category/pagination.js +12 -10
  20. package/dist/base/home/index.js +1 -1
  21. package/dist/base/home/pagination.js +3 -3
  22. package/dist/base/index.d.ts +7 -7
  23. package/dist/base/index.js +7 -7
  24. package/dist/base/layout/RootLayoutBase.d.ts +7 -8
  25. package/dist/base/layout/RootLayoutBase.js +4 -4
  26. package/dist/base/layout/RootLayoutBase.module.css +10 -0
  27. package/dist/base/legal/index.d.ts +4 -4
  28. package/dist/base/legal/index.js +36 -24
  29. package/dist/base/post/index.d.ts +1 -1
  30. package/dist/base/post/index.js +15 -12
  31. package/dist/components/Breadcrumbs/index.d.ts +1 -1
  32. package/dist/components/Breadcrumbs/index.js +4 -4
  33. package/dist/components/JsonLd.d.ts +5 -0
  34. package/dist/components/JsonLd.js +9 -0
  35. package/dist/components/Paginator/index.d.ts +2 -2
  36. package/dist/components/Paginator/index.js +5 -5
  37. package/dist/components/PostCard/index.d.ts +3 -2
  38. package/dist/components/PostCard/index.js +8 -6
  39. package/dist/components/PostCard/index.module.css +7 -7
  40. package/dist/components/PostCard/index.types.d.ts +2 -1
  41. package/dist/components/PostCard/index.utils.d.ts +5 -5
  42. package/dist/components/PostCard/index.utils.js +8 -6
  43. package/dist/components/index.d.ts +6 -5
  44. package/dist/components/index.js +6 -5
  45. package/dist/components/layout/Footer/index.d.ts +6 -6
  46. package/dist/components/layout/Footer/index.js +11 -22
  47. package/dist/components/layout/Footer/index.module.css +59 -9
  48. package/dist/components/layout/Header/HeaderClient.d.ts +2 -2
  49. package/dist/components/layout/Header/HeaderClient.js +22 -2
  50. package/dist/components/layout/Header/index.d.ts +6 -6
  51. package/dist/components/layout/Header/index.js +10 -29
  52. package/dist/components/layout/Header/index.module.css +77 -16
  53. package/dist/components/ui/alert.d.ts +8 -0
  54. package/dist/components/ui/alert.js +12 -0
  55. package/dist/components/ui/alert.module.css +26 -0
  56. package/dist/components/ui/badge.d.ts +3 -0
  57. package/dist/components/ui/badge.js +9 -0
  58. package/dist/components/ui/badge.module.css +23 -0
  59. package/dist/components/ui/breadcrumb.d.ts +8 -0
  60. package/dist/components/ui/breadcrumb.js +22 -0
  61. package/dist/components/ui/breadcrumb.module.css +37 -0
  62. package/dist/components/ui/button.d.ts +7 -0
  63. package/dist/components/ui/button.js +16 -0
  64. package/dist/components/ui/button.module.css +72 -0
  65. package/dist/components/ui/card.d.ts +11 -0
  66. package/dist/components/ui/card.js +16 -0
  67. package/dist/components/ui/card.module.css +37 -0
  68. package/dist/components/ui/cn.d.ts +1 -0
  69. package/dist/components/ui/cn.js +3 -0
  70. package/dist/components/ui/index.d.ts +6 -0
  71. package/dist/components/ui/index.js +6 -0
  72. package/dist/components/ui/pagination.d.ts +14 -0
  73. package/dist/components/ui/pagination.js +25 -0
  74. package/dist/components/ui/pagination.module.css +62 -0
  75. package/dist/index.d.ts +6 -6
  76. package/dist/index.js +6 -6
  77. package/dist/plugins/index.d.ts +1 -1
  78. package/dist/plugins/index.js +1 -1
  79. package/dist/plugins/yoast/index.js +10 -7
  80. package/dist/utils/hreflang.d.ts +5 -7
  81. package/dist/utils/hreflang.js +19 -8
  82. package/dist/utils/html.d.ts +2 -0
  83. package/dist/utils/html.js +25 -0
  84. package/dist/utils/index.d.ts +7 -6
  85. package/dist/utils/index.js +7 -6
  86. package/dist/utils/language.d.ts +37 -0
  87. package/dist/utils/language.js +75 -0
  88. package/dist/utils/seo.d.ts +1 -6
  89. package/dist/utils/seo.js +9 -5
  90. package/dist/utils/sitemap.d.ts +1 -1
  91. package/dist/utils/sitemap.js +181 -63
  92. package/dist/views/CategoryListView/index.d.ts +2 -2
  93. package/dist/views/CategoryListView/index.js +6 -6
  94. package/dist/views/CategoryListView/index.utils.d.ts +1 -1
  95. package/dist/views/CategoryListView/index.utils.js +1 -1
  96. package/dist/views/CategoryPaginationView/index.d.ts +2 -2
  97. package/dist/views/CategoryPaginationView/index.js +6 -6
  98. package/dist/views/CategoryPaginationView/index.utils.d.ts +1 -1
  99. package/dist/views/CategoryPaginationView/index.utils.js +1 -1
  100. package/dist/views/HomePaginationView/index.d.ts +2 -2
  101. package/dist/views/HomePaginationView/index.js +5 -4
  102. package/dist/views/HomePaginationView/index.utils.d.ts +1 -1
  103. package/dist/views/HomePaginationView/index.utils.js +1 -1
  104. package/dist/views/HomeView/index.d.ts +2 -2
  105. package/dist/views/HomeView/index.js +13 -12
  106. package/dist/views/HomeView/index.module.css +51 -8
  107. package/dist/views/HomeView/index.utils.d.ts +1 -1
  108. package/dist/views/HomeView/index.utils.js +2 -2
  109. package/dist/views/LegalPageView/index.d.ts +2 -1
  110. package/dist/views/LegalPageView/index.js +5 -5
  111. package/dist/views/LegalPageView/index.module.css +3 -3
  112. package/dist/views/LegalPageView/index.utils.js +1 -1
  113. package/dist/views/PostView/index.d.ts +3 -2
  114. package/dist/views/PostView/index.js +9 -7
  115. package/dist/views/PostView/index.module.css +46 -13
  116. package/dist/views/PostView/index.utils.d.ts +3 -3
  117. package/dist/views/PostView/index.utils.js +2 -2
  118. package/dist/views/index.d.ts +6 -6
  119. package/dist/views/index.js +6 -6
  120. package/package.json +47 -10
@@ -0,0 +1,75 @@
1
+ const normalizePath = (path) => {
2
+ if (!path || path === "/")
3
+ return "/";
4
+ const withLeadingSlash = path.startsWith("/") ? path : `/${path}`;
5
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
6
+ };
7
+ const defaultSecondaryPath = (code) => normalizePath(code);
8
+ export function normalizeLanguages(languages, options = {}) {
9
+ const fallbackCode = options.defaultCode || options.defaultLocale || "en";
10
+ const source = languages && languages.length > 0
11
+ ? languages
12
+ : [{ code: fallbackCode, locale: options.defaultLocale || fallbackCode }];
13
+ const explicitDefaultIndex = source.findIndex((language) => language.isDefault);
14
+ const defaultIndex = explicitDefaultIndex >= 0 ? explicitDefaultIndex : 0;
15
+ const isMulti = source.length > 1;
16
+ return source.map((language, index) => {
17
+ const isDefault = index === defaultIndex;
18
+ const code = language.code;
19
+ const basePath = language.basePath !== undefined
20
+ ? normalizePath(language.basePath)
21
+ : isMulti && !isDefault
22
+ ? defaultSecondaryPath(code)
23
+ : "/";
24
+ return {
25
+ ...language,
26
+ code,
27
+ locale: language.locale || code,
28
+ basePath: isDefault ? "/" : basePath,
29
+ isDefault,
30
+ prefixed: isMulti && !isDefault,
31
+ };
32
+ });
33
+ }
34
+ export function getLanguageMode(languages) {
35
+ return normalizeLanguages(languages).length > 1 ? "multi" : "single";
36
+ }
37
+ export function getDefaultLanguage(languages) {
38
+ return normalizeLanguages(languages).find((language) => language.isDefault) ?? normalizeLanguages(languages)[0];
39
+ }
40
+ export function getLanguageByCode(languages, code) {
41
+ const normalized = normalizeLanguages(languages);
42
+ if (!code)
43
+ return normalized.find((language) => language.isDefault) ?? normalized[0];
44
+ return normalized.find((language) => language.code === code || language.locale === code);
45
+ }
46
+ export function getLanguageBasePath(language, allLanguages) {
47
+ if (allLanguages) {
48
+ const normalized = normalizeLanguages(allLanguages);
49
+ const match = normalized.find((item) => item.code === language.code) ??
50
+ normalized.find((item) => item.locale === language.locale);
51
+ if (match)
52
+ return match.basePath;
53
+ }
54
+ return normalizeLanguages([language])[0].basePath;
55
+ }
56
+ export function shouldRenderLanguageAlternates(languages) {
57
+ return normalizeLanguages(languages).length > 1;
58
+ }
59
+ export function buildLanguagePath(language, path = "", allLanguages) {
60
+ const basePath = getLanguageBasePath(language, allLanguages);
61
+ const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
62
+ return normalizePath(`${basePath}${normalizedPath}`);
63
+ }
64
+ export function getWpLanguageCode(language) {
65
+ return language?.wpLang;
66
+ }
67
+ export function getWpLanguageFilter(language) {
68
+ if (!language)
69
+ return {};
70
+ if (language.langId) {
71
+ return { taxonomies: { language: [language.langId] } };
72
+ }
73
+ const wpLang = getWpLanguageCode(language);
74
+ return wpLang ? { lang: wpLang } : {};
75
+ }
@@ -20,11 +20,6 @@ type FeaturedMedia = {
20
20
  [key: string]: unknown;
21
21
  };
22
22
  type FeaturedMediaLike = Pick<FeaturedMedia, "source_url">;
23
- type YoastSchemaHolder = {
24
- yoast_head_json?: {
25
- schema?: unknown;
26
- };
27
- };
28
23
  type YoastHolder = {
29
24
  yoast_head_json?: WPYoastHeadJson;
30
25
  };
@@ -46,7 +41,7 @@ export declare function rewriteYoastUrls<T extends WPYoastHeadJson | undefined>(
46
41
  export declare function getYoastHead(entity?: YoastHolder, opts?: RewriteOpts): WPYoastHeadJson | undefined;
47
42
  export declare function buildYoastRobotsMeta(robots?: WPYoastHeadJson["robots"]): Metadata["robots"] | undefined;
48
43
  export declare function isYoastNoindex(yoast?: WPYoastHeadJson): boolean;
49
- export declare function getYoastSchema(entity?: YoastSchemaHolder, opts?: RewriteOpts): unknown;
44
+ export declare function getYoastSchema(entity?: unknown, opts?: RewriteOpts): unknown;
50
45
  export declare function stripBreadcrumbsFromSchema(schema: unknown): unknown;
51
46
  export declare function getFeaturedMedia(entity?: WithEmbeddedMedia): FeaturedMedia | undefined;
52
47
  export declare function getPlainTextExcerpt(html?: string, maxLength?: number): string | undefined;
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;
@@ -196,10 +196,14 @@ export function getFeaturedMedia(entity) {
196
196
  return entity?._embedded?.["wp:featuredmedia"]?.[0];
197
197
  }
198
198
  export function getPlainTextExcerpt(html, maxLength = 160) {
199
- if (!html)
200
- return undefined;
201
- const text = html.replace(/<[^>]*>/g, "");
202
- return maxLength > 0 ? text.slice(0, maxLength) : text;
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;
203
207
  }
204
208
  export function buildBreadcrumbSchema(items) {
205
209
  return {
@@ -1,4 +1,4 @@
1
- import type { LanguageConfig } from "./hreflang";
1
+ import type { LanguageConfig } from "./hreflang.js";
2
2
  export type SitemapRuntimeConfig = {
3
3
  postsPerPagePagination: number;
4
4
  };
@@ -1,28 +1,80 @@
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, getCategoryTotalPages, } from "../base/index.js";
5
+ import { getHomePaginationStaticParams, getHomeTotalPages, } from "../base/home/pagination.js";
6
+ import { getAbsoluteUrl, ensureTrailingSlash } from "./routing.js";
7
+ import { isYoastNoindex } from "./seo.js";
8
+ import { getHreflangCode, 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) => withXDefault(cfg.languages.reduce((acc, langCfg) => {
12
- const basePath = ensureTrailingSlash(langCfg.basePath || "/");
13
- acc[langCfg.code] = getAbsoluteUrl(basePath, resolveSiteUrl(cfg));
14
- return acc;
15
- }, {}));
12
+ const buildHomeAlternates = (cfg) => shouldRenderLanguageAlternates(cfg.languages)
13
+ ? withXDefault(normalizeLanguages(cfg.languages).reduce((acc, langCfg) => {
14
+ const basePath = ensureTrailingSlash(langCfg.basePath || "/");
15
+ acc[getHreflangCode(langCfg)] = getAbsoluteUrl(basePath, resolveSiteUrl(cfg));
16
+ return acc;
17
+ }, {}))
18
+ : undefined;
19
+ async function buildHomePaginationAlternates(cfg, currentPage) {
20
+ if (!shouldRenderLanguageAlternates(cfg.languages))
21
+ return undefined;
22
+ const wpApiUrl = resolveWpApiUrl(cfg);
23
+ if (!isValidWpApiUrl(wpApiUrl))
24
+ return undefined;
25
+ const siteUrl = resolveSiteUrl(cfg);
26
+ const alternates = {};
27
+ for (const langCfg of normalizeLanguages(cfg.languages)) {
28
+ const totalPages = await getHomeTotalPages({
29
+ baseApiUrl: wpApiUrl,
30
+ perPage: cfg.runtimeConfig.postsPerPagePagination,
31
+ langId: langCfg.langId,
32
+ });
33
+ if (totalPages < currentPage)
34
+ continue;
35
+ const basePath = ensureTrailingSlash(langCfg.basePath || "/");
36
+ alternates[getHreflangCode(langCfg)] = getAbsoluteUrl(`${basePath}page/${currentPage}/`, siteUrl);
37
+ }
38
+ return withXDefault(alternates);
39
+ }
40
+ const escapeXml = (value) => value
41
+ .replace(/&/g, "&amp;")
42
+ .replace(/</g, "&lt;")
43
+ .replace(/>/g, "&gt;")
44
+ .replace(/"/g, "&quot;")
45
+ .replace(/'/g, "&apos;");
46
+ const renderAlternates = (alternates) => {
47
+ const normalizedAlternates = withXDefault(alternates);
48
+ if (!normalizedAlternates)
49
+ return undefined;
50
+ return Object.entries(normalizedAlternates)
51
+ .map(([code, href]) => `<xhtml:link rel="alternate" hreflang="${escapeXml(code)}" href="${escapeXml(href)}" />`)
52
+ .join("");
53
+ };
16
54
  const getYoastHeadJson = (entity) => entity.yoast_head_json;
17
55
  const isNoindexEntity = (entity) => isYoastNoindex(getYoastHeadJson(entity));
56
+ const getWpLangParam = (language) => {
57
+ const lang = language.wpLang || (language.langId ? language.code : undefined);
58
+ return lang ? { lang } : {};
59
+ };
60
+ async function mapConcurrent(items, mapper, concurrency = 5) {
61
+ const results = new Array(items.length);
62
+ let nextIndex = 0;
63
+ async function worker() {
64
+ while (nextIndex < items.length) {
65
+ const currentIndex = nextIndex;
66
+ nextIndex += 1;
67
+ results[currentIndex] = await mapper(items[currentIndex], currentIndex);
68
+ }
69
+ }
70
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
71
+ return results;
72
+ }
18
73
  export function renderSitemap(entries) {
19
74
  const items = entries
20
75
  .map((entry) => {
21
- const links = entry.alternates &&
22
- Object.entries(entry.alternates)
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>`;
76
+ const links = renderAlternates(entry.alternates);
77
+ return `<url><loc>${escapeXml(entry.url)}</loc>${entry.lastmod ? `<lastmod>${escapeXml(entry.lastmod)}</lastmod>` : ""}${links ?? ""}</url>`;
26
78
  })
27
79
  .join("");
28
80
  return (`<?xml version="1.0" encoding="UTF-8"?>` +
@@ -32,7 +84,7 @@ export function renderSitemap(entries) {
32
84
  }
33
85
  export function renderSitemapIndex(sitemaps) {
34
86
  const items = sitemaps
35
- .map((s) => `<sitemap><loc>${s.url}</loc>${s.lastmod ? `<lastmod>${s.lastmod}</lastmod>` : ""}</sitemap>`)
87
+ .map((s) => `<sitemap><loc>${escapeXml(s.url)}</loc>${s.lastmod ? `<lastmod>${escapeXml(s.lastmod)}</lastmod>` : ""}</sitemap>`)
36
88
  .join("");
37
89
  return (`<?xml version="1.0" encoding="UTF-8"?>` +
38
90
  `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
@@ -40,38 +92,42 @@ export function renderSitemapIndex(sitemaps) {
40
92
  `</sitemapindex>`);
41
93
  }
42
94
  export async function getHomeEntries(cfg) {
43
- const entries = [];
44
- const { languages, runtimeConfig } = cfg;
95
+ const { runtimeConfig } = cfg;
96
+ const languages = normalizeLanguages(cfg.languages);
45
97
  const wpApiUrl = resolveWpApiUrl(cfg);
46
98
  const siteUrl = resolveSiteUrl(cfg);
47
99
  const homeAlternates = buildHomeAlternates(cfg);
48
- for (const langCfg of languages) {
100
+ return mapConcurrent(languages, async (langCfg) => {
49
101
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
50
102
  const homeUrl = getAbsoluteUrl(basePath, siteUrl);
51
- const homePosts = await listPostsWithEmbeds({
52
- baseApiUrl: wpApiUrl,
53
- perPage: runtimeConfig.postsPerPagePagination,
54
- page: 1,
55
- langId: langCfg.langId,
56
- orderby: "date",
57
- order: "desc",
58
- });
59
- const latest = homePosts.ok ? homePosts.data?.items[0] : undefined;
103
+ const homePosts = isValidWpApiUrl(wpApiUrl)
104
+ ? await listPostsWithEmbeds({
105
+ baseApiUrl: wpApiUrl,
106
+ perPage: runtimeConfig.postsPerPagePagination,
107
+ page: 1,
108
+ langId: langCfg.langId,
109
+ orderby: "date",
110
+ order: "desc",
111
+ })
112
+ : null;
113
+ const latest = homePosts?.ok ? homePosts.data?.items[0] : undefined;
60
114
  const lastmod = latest?.modified ||
61
115
  latest?.date;
62
- entries.push({
116
+ return {
63
117
  url: homeUrl,
64
118
  lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
65
119
  alternates: homeAlternates ?? undefined,
66
- });
67
- }
68
- return entries;
120
+ };
121
+ });
69
122
  }
70
123
  export async function getHomePaginationEntries(cfg) {
71
124
  const entries = [];
72
- const { languages, runtimeConfig } = cfg;
125
+ const { runtimeConfig } = cfg;
126
+ const languages = normalizeLanguages(cfg.languages);
73
127
  const wpApiUrl = resolveWpApiUrl(cfg);
74
128
  const siteUrl = resolveSiteUrl(cfg);
129
+ if (!isValidWpApiUrl(wpApiUrl))
130
+ return entries;
75
131
  for (const langCfg of languages) {
76
132
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
77
133
  const pages = await getHomePaginationStaticParams({
@@ -79,7 +135,7 @@ export async function getHomePaginationEntries(cfg) {
79
135
  perPage: runtimeConfig.postsPerPagePagination,
80
136
  langId: langCfg.langId,
81
137
  });
82
- for (const p of pages) {
138
+ const pageEntries = await mapConcurrent(pages, async (p) => {
83
139
  const url = getAbsoluteUrl(`${basePath}page/${p.page}/`, siteUrl);
84
140
  const pagePosts = await listPostsWithEmbeds({
85
141
  baseApiUrl: wpApiUrl,
@@ -92,31 +148,39 @@ export async function getHomePaginationEntries(cfg) {
92
148
  const latest = pagePosts.ok ? pagePosts.data?.items[0] : undefined;
93
149
  const lastmod = latest?.modified ||
94
150
  latest?.date;
95
- entries.push({
151
+ return {
96
152
  url,
97
153
  lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
98
- });
99
- }
154
+ alternates: await buildHomePaginationAlternates(cfg, Number(p.page)),
155
+ };
156
+ });
157
+ entries.push(...pageEntries);
100
158
  }
101
159
  return entries;
102
160
  }
103
161
  export async function getCategoryEntries(cfg) {
104
162
  const entries = [];
105
- const { languages, runtimeConfig } = cfg;
163
+ const { runtimeConfig } = cfg;
164
+ const languages = normalizeLanguages(cfg.languages);
106
165
  const wpApiUrl = resolveWpApiUrl(cfg);
107
166
  const siteUrl = resolveSiteUrl(cfg);
167
+ if (!isValidWpApiUrl(wpApiUrl))
168
+ return entries;
169
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
108
170
  const categoriesPerLang = {};
109
171
  const categoryAlternates = {};
110
172
  for (const langCfg of languages) {
111
173
  const { categoriesApi } = createWpClient({ baseUrl: wpApiUrl });
112
174
  const categories = await categoriesApi.listAll({
113
- ...(langCfg.langId ? { lang: langCfg.code } : {}),
175
+ ...getWpLangParam(langCfg),
114
176
  _fields: ["id", "slug", "name", "description", "meta", "yoast_head_json"],
115
177
  });
116
178
  const indexableCategories = categories.filter((cat) => !isNoindexEntity(cat));
117
179
  categoriesPerLang[langCfg.code] = indexableCategories;
118
180
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
119
181
  for (const cat of indexableCategories) {
182
+ if (!renderAlternatesForLanguages)
183
+ continue;
120
184
  const translationKey = getTranslationKey(cat);
121
185
  if (!translationKey)
122
186
  continue;
@@ -124,13 +188,13 @@ export async function getCategoryEntries(cfg) {
124
188
  if (!categoryAlternates[translationKey]) {
125
189
  categoryAlternates[translationKey] = {};
126
190
  }
127
- categoryAlternates[translationKey][langCfg.code] = url;
191
+ categoryAlternates[translationKey][getHreflangCode(langCfg)] = url;
128
192
  }
129
193
  }
130
194
  for (const langCfg of languages) {
131
195
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
132
196
  const categories = categoriesPerLang[langCfg.code] ?? [];
133
- for (const cat of categories) {
197
+ const categoryEntries = await mapConcurrent(categories, async (cat) => {
134
198
  const url = getAbsoluteUrl(`${basePath}${cat.slug}/`, siteUrl);
135
199
  const catPosts = await listPostsWithEmbeds({
136
200
  baseApiUrl: wpApiUrl,
@@ -145,49 +209,84 @@ export async function getCategoryEntries(cfg) {
145
209
  const lastmod = latest?.modified ||
146
210
  latest?.date;
147
211
  const alt = (() => {
212
+ if (!renderAlternatesForLanguages)
213
+ return undefined;
148
214
  const key = getTranslationKey(cat);
149
215
  if (!key)
150
216
  return undefined;
151
217
  const a = withXDefault(categoryAlternates[key]);
152
218
  return a;
153
219
  })();
154
- entries.push({
220
+ return {
155
221
  url,
156
222
  lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
157
223
  alternates: alt,
158
- });
159
- }
224
+ };
225
+ });
226
+ entries.push(...categoryEntries);
160
227
  }
161
228
  return entries;
162
229
  }
163
230
  export async function getCategoryPaginationEntries(cfg) {
164
231
  const entries = [];
165
- const { languages, runtimeConfig } = cfg;
232
+ const { runtimeConfig } = cfg;
233
+ const languages = normalizeLanguages(cfg.languages);
166
234
  const wpApiUrl = resolveWpApiUrl(cfg);
167
235
  const siteUrl = resolveSiteUrl(cfg);
236
+ if (!isValidWpApiUrl(wpApiUrl))
237
+ return entries;
238
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
168
239
  const catIdsBySlugByLang = {};
240
+ const catsBySlugByLang = {};
241
+ const alternatesByTranslationPage = {};
169
242
  for (const langCfg of languages) {
170
243
  const { categoriesApi } = createWpClient({ baseUrl: wpApiUrl });
171
244
  const categories = await categoriesApi.listAll({
172
- lang: langCfg.code,
173
- _fields: ["id", "slug", "yoast_head_json"],
245
+ ...getWpLangParam(langCfg),
246
+ _fields: ["id", "slug", "meta", "yoast_head_json"],
174
247
  });
175
248
  const indexableCategories = categories.filter((cat) => !isNoindexEntity(cat));
176
249
  catIdsBySlugByLang[langCfg.code] = Object.fromEntries(indexableCategories.map((c) => [c.slug, c.id]));
250
+ catsBySlugByLang[langCfg.code] = Object.fromEntries(indexableCategories.map((c) => [c.slug, c]));
251
+ const basePath = ensureTrailingSlash(langCfg.basePath || "/");
252
+ for (const category of indexableCategories) {
253
+ if (!renderAlternatesForLanguages)
254
+ continue;
255
+ const translationKey = getTranslationKey(category);
256
+ if (!translationKey)
257
+ continue;
258
+ const totalPages = await getCategoryTotalPages({
259
+ baseApiUrl: wpApiUrl,
260
+ categoryId: category.id,
261
+ perPage: runtimeConfig.postsPerPagePagination,
262
+ langId: langCfg.langId,
263
+ });
264
+ for (let page = 2; page <= totalPages; page++) {
265
+ const pageKey = String(page);
266
+ if (!alternatesByTranslationPage[translationKey]) {
267
+ alternatesByTranslationPage[translationKey] = {};
268
+ }
269
+ if (!alternatesByTranslationPage[translationKey][pageKey]) {
270
+ alternatesByTranslationPage[translationKey][pageKey] = {};
271
+ }
272
+ alternatesByTranslationPage[translationKey][pageKey][getHreflangCode(langCfg)] = getAbsoluteUrl(`${basePath}${category.slug}/page/${page}/`, siteUrl);
273
+ }
274
+ }
177
275
  }
178
276
  for (const langCfg of languages) {
179
277
  const catPages = await getCategoryPaginationStaticParams({
180
278
  baseApiUrl: wpApiUrl,
181
279
  perPage: runtimeConfig.postsPerPagePagination,
182
- lang: langCfg.code,
280
+ lang: langCfg.wpLang || (langCfg.langId ? langCfg.code : undefined) || "",
183
281
  langId: langCfg.langId,
184
282
  });
185
283
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
186
- for (const page of catPages) {
284
+ const pageEntries = await mapConcurrent(catPages, async (page) => {
187
285
  const url = getAbsoluteUrl(`${basePath}${page.category}/page/${page.page}/`, siteUrl);
188
286
  const catId = catIdsBySlugByLang[langCfg.code]?.[page.category];
189
287
  if (!catId)
190
- continue;
288
+ return null;
289
+ const category = catsBySlugByLang[langCfg.code]?.[page.category];
191
290
  let lastmod;
192
291
  const pagePosts = await listPostsWithEmbeds({
193
292
  baseApiUrl: wpApiUrl,
@@ -202,22 +301,29 @@ export async function getCategoryPaginationEntries(cfg) {
202
301
  lastmod =
203
302
  latest?.modified ||
204
303
  latest?.date;
205
- entries.push({
304
+ return {
206
305
  url,
207
306
  lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
208
- });
209
- }
307
+ alternates: renderAlternatesForLanguages && category
308
+ ? withXDefault(alternatesByTranslationPage[getTranslationKey(category) || ""]?.[page.page])
309
+ : undefined,
310
+ };
311
+ });
312
+ entries.push(...pageEntries.filter(Boolean));
210
313
  }
211
314
  return entries;
212
315
  }
213
316
  export async function getPostEntriesForLang(langCode, cfg) {
214
- const { languages } = cfg;
317
+ const languages = normalizeLanguages(cfg.languages);
215
318
  const langCfg = languages.find((l) => l.code === langCode);
216
319
  if (!langCfg)
217
320
  return [];
218
321
  const wpApiUrl = resolveWpApiUrl(cfg);
322
+ if (!isValidWpApiUrl(wpApiUrl))
323
+ return [];
219
324
  const siteUrl = resolveSiteUrl(cfg);
220
325
  const postsApi = createPostsEndpoints({ baseUrl: wpApiUrl });
326
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
221
327
  const languageFilter = langCfg.langId
222
328
  ? { taxonomies: { language: [langCfg.langId] } }
223
329
  : {};
@@ -228,7 +334,7 @@ export async function getPostEntriesForLang(langCode, cfg) {
228
334
  });
229
335
  const indexablePosts = posts.filter((post) => !isNoindexEntity(post));
230
336
  const postAlternates = {};
231
- for (const l of languages) {
337
+ const postsByLanguage = await mapConcurrent(languages, async (l) => {
232
338
  const base = ensureTrailingSlash(l.basePath || "/");
233
339
  const langPosts = l.code === langCfg.code
234
340
  ? indexablePosts
@@ -240,7 +346,12 @@ export async function getPostEntriesForLang(langCode, cfg) {
240
346
  const filteredLangPosts = l.code === langCfg.code
241
347
  ? langPosts
242
348
  : langPosts.filter((post) => !isNoindexEntity(post));
349
+ return { language: l, base, posts: filteredLangPosts };
350
+ });
351
+ for (const { language: l, base, posts: filteredLangPosts } of postsByLanguage) {
243
352
  for (const p of filteredLangPosts) {
353
+ if (!renderAlternatesForLanguages)
354
+ continue;
244
355
  const translationKey = getTranslationKey(p);
245
356
  if (!translationKey)
246
357
  continue;
@@ -251,7 +362,7 @@ export async function getPostEntriesForLang(langCode, cfg) {
251
362
  const url = getAbsoluteUrl(`${base}${categorySlug}/${p.slug}/`, siteUrl);
252
363
  if (!postAlternates[translationKey])
253
364
  postAlternates[translationKey] = {};
254
- postAlternates[translationKey][l.code] = url;
365
+ postAlternates[translationKey][getHreflangCode(l)] = url;
255
366
  }
256
367
  }
257
368
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
@@ -265,7 +376,7 @@ export async function getPostEntriesForLang(langCode, cfg) {
265
376
  const lastmod = p.modified ||
266
377
  p.date;
267
378
  const translationKey = getTranslationKey(p);
268
- const alts = translationKey
379
+ const alts = renderAlternatesForLanguages && translationKey
269
380
  ? withXDefault(postAlternates[translationKey])
270
381
  : undefined;
271
382
  return {
@@ -278,9 +389,12 @@ export async function getPostEntriesForLang(langCode, cfg) {
278
389
  }
279
390
  export async function getLegalEntries(cfg) {
280
391
  const entries = [];
281
- const { languages } = cfg;
392
+ const languages = normalizeLanguages(cfg.languages);
282
393
  const siteUrl = resolveSiteUrl(cfg);
283
394
  const wpApiUrl = resolveWpApiUrl(cfg);
395
+ if (!isValidWpApiUrl(wpApiUrl))
396
+ return entries;
397
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
284
398
  const pagesApi = createPageEndpoints({ baseUrl: wpApiUrl });
285
399
  const pagesPerLang = {};
286
400
  const alternatesByKey = {};
@@ -306,18 +420,22 @@ export async function getLegalEntries(cfg) {
306
420
  "yoast_head_json",
307
421
  ],
308
422
  });
309
- const filtered = pages.filter((p) => getEntityLanguageId(p)?.toString() === langCfg.langId?.toString());
423
+ const filtered = langCfg.langId
424
+ ? pages.filter((p) => getEntityLanguageId(p)?.toString() === langCfg.langId?.toString())
425
+ : pages;
310
426
  const indexablePages = filtered.filter((page) => !isNoindexEntity(page));
311
427
  pagesPerLang[langCfg.code] = indexablePages;
312
428
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
313
429
  for (const p of indexablePages) {
430
+ if (!renderAlternatesForLanguages)
431
+ continue;
314
432
  const translationKey = getTranslationKey(p);
315
433
  if (!translationKey)
316
434
  continue;
317
435
  const url = getAbsoluteUrl(`${basePath}legal/${p.slug}/`, siteUrl);
318
436
  if (!alternatesByKey[translationKey])
319
437
  alternatesByKey[translationKey] = {};
320
- alternatesByKey[translationKey][langCfg.code] = url;
438
+ alternatesByKey[translationKey][getHreflangCode(langCfg)] = url;
321
439
  }
322
440
  }
323
441
  for (const langCfg of languages) {
@@ -327,7 +445,7 @@ export async function getLegalEntries(cfg) {
327
445
  const url = getAbsoluteUrl(`${basePath}legal/${p.slug}/`, siteUrl);
328
446
  const lastmod = p.modified || p.date;
329
447
  const translationKey = getTranslationKey(p);
330
- const alts = translationKey
448
+ const alts = renderAlternatesForLanguages && translationKey
331
449
  ? withXDefault(alternatesByKey[translationKey])
332
450
  : undefined;
333
451
  entries.push({
@@ -1,5 +1,5 @@
1
1
  import { type WPCategory, type WPPost } from "wpjsapi-lib";
2
- import { type Locale } from "../../utils";
2
+ import { type Locale } from "../../utils/index.js";
3
3
  type CategoryListViewProps = {
4
4
  category: WPCategory;
5
5
  posts: WPPost[];
@@ -10,5 +10,5 @@ type CategoryListViewProps = {
10
10
  locale?: Locale;
11
11
  dateLocale?: string;
12
12
  };
13
- export declare function CategoryListView({ category, posts, totalPages, baseUrl, homeHref, linkBasePath, locale, dateLocale, }: CategoryListViewProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function CategoryListView({ category, posts, totalPages, baseUrl, homeHref, linkBasePath, locale, dateLocale, }: CategoryListViewProps): import("react").JSX.Element;
14
14
  export {};
@@ -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: `container ${styles.container}`, children: [_jsxs("header", { className: styles.header, children: [_jsx(Breadcrumbs, { items: [
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,5 +1,5 @@
1
1
  import { type WPCategory, type WPPost } from "wpjsapi-lib";
2
- import { type Locale } from "../../utils";
2
+ import { type Locale } from "../../utils/index.js";
3
3
  type CategoryPaginationViewProps = {
4
4
  category: WPCategory;
5
5
  posts: WPPost[];
@@ -10,5 +10,5 @@ type CategoryPaginationViewProps = {
10
10
  locale?: Locale;
11
11
  dateLocale?: string;
12
12
  };
13
- export declare function CategoryPaginationView({ category, posts, currentPage, totalPages, baseUrl, linkBasePath, locale, dateLocale, }: CategoryPaginationViewProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function CategoryPaginationView({ category, posts, currentPage, totalPages, baseUrl, linkBasePath, locale, dateLocale, }: CategoryPaginationViewProps): import("react").JSX.Element;
14
14
  export {};