wpheadless-lib 1.1.10 → 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.
Files changed (119) 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 +33 -121
  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 +6 -7
  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.js +4 -4
  32. package/dist/components/JsonLd.d.ts +5 -0
  33. package/dist/components/JsonLd.js +9 -0
  34. package/dist/components/Paginator/index.d.ts +1 -1
  35. package/dist/components/Paginator/index.js +5 -5
  36. package/dist/components/PostCard/index.d.ts +3 -2
  37. package/dist/components/PostCard/index.js +8 -6
  38. package/dist/components/PostCard/index.module.css +7 -7
  39. package/dist/components/PostCard/index.types.d.ts +2 -1
  40. package/dist/components/PostCard/index.utils.d.ts +5 -5
  41. package/dist/components/PostCard/index.utils.js +8 -6
  42. package/dist/components/index.d.ts +6 -5
  43. package/dist/components/index.js +6 -5
  44. package/dist/components/layout/Footer/index.d.ts +5 -5
  45. package/dist/components/layout/Footer/index.js +11 -22
  46. package/dist/components/layout/Footer/index.module.css +59 -9
  47. package/dist/components/layout/Header/HeaderClient.d.ts +1 -1
  48. package/dist/components/layout/Header/HeaderClient.js +22 -2
  49. package/dist/components/layout/Header/index.d.ts +5 -5
  50. package/dist/components/layout/Header/index.js +10 -29
  51. package/dist/components/layout/Header/index.module.css +77 -16
  52. package/dist/components/ui/alert.d.ts +8 -0
  53. package/dist/components/ui/alert.js +12 -0
  54. package/dist/components/ui/alert.module.css +26 -0
  55. package/dist/components/ui/badge.d.ts +3 -0
  56. package/dist/components/ui/badge.js +9 -0
  57. package/dist/components/ui/badge.module.css +23 -0
  58. package/dist/components/ui/breadcrumb.d.ts +8 -0
  59. package/dist/components/ui/breadcrumb.js +22 -0
  60. package/dist/components/ui/breadcrumb.module.css +37 -0
  61. package/dist/components/ui/button.d.ts +7 -0
  62. package/dist/components/ui/button.js +16 -0
  63. package/dist/components/ui/button.module.css +72 -0
  64. package/dist/components/ui/card.d.ts +11 -0
  65. package/dist/components/ui/card.js +16 -0
  66. package/dist/components/ui/card.module.css +37 -0
  67. package/dist/components/ui/cn.d.ts +1 -0
  68. package/dist/components/ui/cn.js +3 -0
  69. package/dist/components/ui/index.d.ts +6 -0
  70. package/dist/components/ui/index.js +6 -0
  71. package/dist/components/ui/pagination.d.ts +14 -0
  72. package/dist/components/ui/pagination.js +25 -0
  73. package/dist/components/ui/pagination.module.css +62 -0
  74. package/dist/index.d.ts +6 -6
  75. package/dist/index.js +6 -6
  76. package/dist/plugins/index.d.ts +1 -1
  77. package/dist/plugins/index.js +1 -1
  78. package/dist/plugins/yoast/index.js +10 -7
  79. package/dist/utils/hreflang.d.ts +4 -7
  80. package/dist/utils/hreflang.js +11 -6
  81. package/dist/utils/html.d.ts +2 -0
  82. package/dist/utils/html.js +25 -0
  83. package/dist/utils/index.d.ts +7 -6
  84. package/dist/utils/index.js +7 -6
  85. package/dist/utils/language.d.ts +37 -0
  86. package/dist/utils/language.js +75 -0
  87. package/dist/utils/seo.d.ts +1 -6
  88. package/dist/utils/seo.js +9 -5
  89. package/dist/utils/sitemap.d.ts +1 -1
  90. package/dist/utils/sitemap.js +107 -56
  91. package/dist/views/CategoryListView/index.d.ts +1 -1
  92. package/dist/views/CategoryListView/index.js +6 -6
  93. package/dist/views/CategoryListView/index.utils.d.ts +1 -1
  94. package/dist/views/CategoryListView/index.utils.js +1 -1
  95. package/dist/views/CategoryPaginationView/index.d.ts +1 -1
  96. package/dist/views/CategoryPaginationView/index.js +6 -6
  97. package/dist/views/CategoryPaginationView/index.utils.d.ts +1 -1
  98. package/dist/views/CategoryPaginationView/index.utils.js +1 -1
  99. package/dist/views/HomePaginationView/index.d.ts +1 -1
  100. package/dist/views/HomePaginationView/index.js +5 -4
  101. package/dist/views/HomePaginationView/index.utils.d.ts +1 -1
  102. package/dist/views/HomePaginationView/index.utils.js +1 -1
  103. package/dist/views/HomeView/index.d.ts +1 -1
  104. package/dist/views/HomeView/index.js +13 -12
  105. package/dist/views/HomeView/index.module.css +51 -8
  106. package/dist/views/HomeView/index.utils.d.ts +1 -1
  107. package/dist/views/HomeView/index.utils.js +2 -2
  108. package/dist/views/LegalPageView/index.d.ts +2 -1
  109. package/dist/views/LegalPageView/index.js +5 -5
  110. package/dist/views/LegalPageView/index.module.css +3 -3
  111. package/dist/views/LegalPageView/index.utils.js +1 -1
  112. package/dist/views/PostView/index.d.ts +3 -2
  113. package/dist/views/PostView/index.js +9 -7
  114. package/dist/views/PostView/index.module.css +46 -13
  115. package/dist/views/PostView/index.utils.d.ts +3 -3
  116. package/dist/views/PostView/index.utils.js +2 -2
  117. package/dist/views/index.d.ts +6 -6
  118. package/dist/views/index.js +6 -6
  119. package/package.json +47 -10
@@ -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) => 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[langCfg.code] = getAbsoluteUrl(basePath, resolveSiteUrl(cfg));
16
+ return acc;
17
+ }, {}))
18
+ : undefined;
19
+ const escapeXml = (value) => value
20
+ .replace(/&/g, "&")
21
+ .replace(/</g, "&lt;")
22
+ .replace(/>/g, "&gt;")
23
+ .replace(/"/g, "&quot;")
24
+ .replace(/'/g, "&apos;");
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
- 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>`;
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 entries = [];
44
- const { languages, runtimeConfig } = cfg;
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
- for (const langCfg of languages) {
79
+ return mapConcurrent(languages, async (langCfg) => {
49
80
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
50
81
  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;
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
- entries.push({
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 { languages, runtimeConfig } = cfg;
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
- for (const p of pages) {
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
- entries.push({
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 { languages, runtimeConfig } = cfg;
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.langId ? { lang: langCfg.code } : {}),
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
- for (const cat of categories) {
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
- entries.push({
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 { languages, runtimeConfig } = cfg;
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
- lang: langCfg.code,
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
- for (const page of catPages) {
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
- continue;
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
- entries.push({
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 { languages } = cfg;
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
- for (const l of languages) {
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 { languages } = cfg;
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,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[];
@@ -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[];
@@ -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: `container ${styles.container}`, children: [_jsxs("header", { className: styles.header, children: [_jsx(Breadcrumbs, { items: [
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,5 +1,5 @@
1
1
  import { type WPPost } from "wpjsapi-lib";
2
- import { type Locale } from "../../utils";
2
+ import { type Locale } from "../../utils/index.js";
3
3
  type HomePaginationViewProps = {
4
4
  posts: WPPost[];
5
5
  currentPage: number;
@@ -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: `container ${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("div", { className: `card ${styles.errorCard}`, children: copy.errorText && _jsx("p", { 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
+ 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,4 +1,4 @@
1
- import { type Locale } from "../../utils";
1
+ import { type Locale } from "../../utils/index.js";
2
2
  export declare const buildHomePaginationCopy: (locale: Locale | undefined, page: number, error?: string | null) => {
3
3
  title: string;
4
4
  subtitle: string;
@@ -1,4 +1,4 @@
1
- import { getTranslator } from "../../utils";
1
+ import { getTranslator } from "../../utils/index.js";
2
2
  export const buildHomePaginationCopy = (locale, page, error) => {
3
3
  const t = getTranslator(locale);
4
4
  return {
@@ -1,5 +1,5 @@
1
1
  import { type WPPost } from "wpjsapi-lib";
2
- import { type Locale } from "../../utils";
2
+ import { type Locale } from "../../utils/index.js";
3
3
  type HomeViewProps = {
4
4
  featuredPost?: WPPost;
5
5
  regularPosts: WPPost[];
@@ -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)}...` : trimmed;
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: `container ${styles.container}`, children: error ? (_jsxs("div", { className: styles.errorBox, children: [_jsxs("p", { children: [copy.errorTitle, ": ", error] }), _jsx("p", { className: styles.errorHint, children: copy.errorHint })] })) : !featuredModel && regularPosts.length === 0 ? (_jsx("p", { className: styles.empty, children: copy.empty })) : (_jsxs(_Fragment, { children: [featuredModel && (_jsxs("article", { className: `card ${styles.featured} ${featuredModel.image
18
- ? styles.featuredWithImage
19
- : styles.featuredNoImage}`, children: [featuredModel.image && (_jsx(Link, { href: featuredModel.postUrl, className: styles.featuredImageLink, children: _jsx(Image, { src: featuredModel.image.src, alt: featuredModel.image.alt, fill: true, className: styles.featuredImage, priority: true, sizes: "(max-width: 768px) 100vw, 50vw" }) })), _jsxs("div", { className: styles.featuredBody, children: [featuredModel.badgeLabel &&
20
- (featuredModel.badgeUrl ? (_jsx(Link, { href: featuredModel.badgeUrl, className: `${styles.featuredBadge} badge`, title: featuredModel.badgeLabel, children: truncateLabel(featuredModel.badgeLabel) })) : (_jsx("span", { className: `${styles.featuredBadge} badge`, title: featuredModel.badgeLabel, children: truncateLabel(featuredModel.badgeLabel) }))), _jsx(Link, { href: featuredModel.postUrl, children: _jsx("h2", { dangerouslySetInnerHTML: {
21
- __html: featuredModel.titleHtml,
22
- }, className: styles.featuredTitle }) }), _jsx("div", { dangerouslySetInnerHTML: {
23
- __html: featuredModel.excerptHtml,
24
- }, 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 })] })) }));
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
  }
@@ -1,5 +1,5 @@
1
1
  .container {
2
- padding-top: var(--spacing-lg);
2
+ padding-top: var(--spacing-xl);
3
3
  padding-bottom: var(--spacing-lg);
4
4
  }
5
5
 
@@ -21,22 +21,45 @@
21
21
  color: var(--foreground-light);
22
22
  }
23
23
 
24
+ .visuallyHidden {
25
+ position: absolute;
26
+ width: 1px;
27
+ height: 1px;
28
+ padding: 0;
29
+ margin: -1px;
30
+ overflow: hidden;
31
+ clip: rect(0, 0, 0, 0);
32
+ white-space: nowrap;
33
+ border: 0;
34
+ }
35
+
36
+ .masthead {
37
+ display: grid;
38
+ gap: clamp(1.25rem, 3vw, 2rem);
39
+ margin-bottom: var(--spacing-xl);
40
+ }
41
+
24
42
  .featured {
25
- margin-bottom: var(--spacing-lg);
43
+ position: relative;
26
44
  display: grid;
45
+ min-height: clamp(18rem, 34vw, 27rem);
46
+ overflow: hidden;
47
+ border-radius: 12px;
48
+ background: var(--card-bg);
49
+ box-shadow: 0 14px 38px var(--shadow);
27
50
  }
28
51
 
29
52
  .featuredWithImage {
30
- grid-template-columns: 1fr 1fr;
53
+ grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
31
54
  }
32
55
 
33
56
  .featuredNoImage {
34
57
  grid-template-columns: 1fr;
35
58
  }
36
59
 
37
- .featuredImageLink {
60
+ .featuredImageFrame {
38
61
  position: relative;
39
- min-height: 400px;
62
+ min-height: 100%;
40
63
  overflow: hidden;
41
64
  }
42
65
 
@@ -44,8 +67,17 @@
44
67
  object-fit: cover;
45
68
  }
46
69
 
70
+ .featuredOverlay {
71
+ position: absolute;
72
+ inset: 0;
73
+ background:
74
+ linear-gradient(18deg, rgba(0, 0, 0, 0.52), transparent 62%),
75
+ linear-gradient(0deg, rgba(0, 0, 0, 0.24), transparent 48%);
76
+ pointer-events: none;
77
+ }
78
+
47
79
  .featuredBody {
48
- padding: var(--spacing-lg);
80
+ padding: clamp(1.5rem, 4vw, 3rem);
49
81
  display: flex;
50
82
  flex-direction: column;
51
83
  justify-content: center;
@@ -58,12 +90,19 @@
58
90
 
59
91
  .featuredTitle {
60
92
  margin-bottom: var(--spacing-sm);
61
- font-size: 2rem;
93
+ font-size: clamp(1.65rem, 3.5vw, 2.7rem);
94
+ line-height: 1.08;
62
95
  margin-top: 0;
63
96
  }
64
97
 
98
+ .featuredTitle:hover {
99
+ color: var(--primary);
100
+ }
101
+
65
102
  .featuredExcerpt {
66
103
  margin-bottom: var(--spacing-sm);
104
+ font-size: 1rem;
105
+ line-height: 1.65;
67
106
  }
68
107
 
69
108
  .featuredDate {
@@ -82,7 +121,11 @@
82
121
  grid-template-columns: 1fr;
83
122
  }
84
123
 
85
- .featuredImageLink {
124
+ .featured {
125
+ min-height: 0;
126
+ }
127
+
128
+ .featuredImageFrame {
86
129
  min-height: 220px;
87
130
  }
88
131
 
@@ -1,5 +1,5 @@
1
1
  import type { WPPost } from "wpjsapi-lib";
2
- import { type Locale } from "../../utils";
2
+ import { type Locale } from "../../utils/index.js";
3
3
  export type HomeCopy = {
4
4
  title: string;
5
5
  empty: string;
@@ -1,5 +1,5 @@
1
- import { getFeaturedMedia, getTranslator, stripTags, } from "../../utils";
2
- import { buildCategoryUrl, buildPostUrl, formatPostDate, } from "../../components/PostCard/index.utils";
1
+ import { getFeaturedMedia, getTranslator, stripTags, } from "../../utils/index.js";
2
+ import { buildCategoryUrl, buildPostUrl, formatPostDate, } from "../../components/PostCard/index.utils.js";
3
3
  export const getHomeCopy = (locale) => {
4
4
  const t = getTranslator(locale);
5
5
  return {
@@ -3,6 +3,7 @@ type LegalPageViewProps = {
3
3
  page: WPPage;
4
4
  dateLocale?: string;
5
5
  homeHref: string;
6
+ priorityImage?: boolean;
6
7
  };
7
- export declare function LegalPageView({ page, homeHref }: LegalPageViewProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function LegalPageView({ page, homeHref, priorityImage, }: LegalPageViewProps): import("react/jsx-runtime").JSX.Element;
8
9
  export {};