wpheadless-lib 1.1.11 → 1.1.14

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 (38) hide show
  1. package/dist/app/globals.css +2 -3
  2. package/dist/base/layout/RootLayoutBase.d.ts +1 -1
  3. package/dist/base/legal/index.js +7 -6
  4. package/dist/components/Breadcrumbs/index.d.ts +1 -1
  5. package/dist/components/JsonLd.d.ts +1 -1
  6. package/dist/components/LanguageLinks/index.d.ts +13 -0
  7. package/dist/components/LanguageLinks/index.js +9 -0
  8. package/dist/components/LanguageLinks/index.module.css +78 -0
  9. package/dist/components/Paginator/index.d.ts +1 -1
  10. package/dist/components/PostCard/index.d.ts +1 -1
  11. package/dist/components/index.d.ts +2 -0
  12. package/dist/components/index.js +1 -0
  13. package/dist/components/layout/Footer/index.d.ts +1 -1
  14. package/dist/components/layout/Header/HeaderClient.d.ts +1 -1
  15. package/dist/components/layout/Header/index.d.ts +1 -1
  16. package/dist/components/ui/alert.d.ts +3 -3
  17. package/dist/components/ui/badge.d.ts +1 -1
  18. package/dist/components/ui/breadcrumb.d.ts +6 -6
  19. package/dist/components/ui/button.d.ts +1 -1
  20. package/dist/components/ui/card.d.ts +4 -4
  21. package/dist/components/ui/pagination.d.ts +7 -7
  22. package/dist/utils/hreflang.d.ts +7 -0
  23. package/dist/utils/hreflang.js +35 -2
  24. package/dist/utils/html.d.ts +5 -3
  25. package/dist/utils/html.js +48 -0
  26. package/dist/utils/language.d.ts +2 -0
  27. package/dist/utils/sitemap.js +81 -11
  28. package/dist/views/CategoryListView/index.d.ts +3 -1
  29. package/dist/views/CategoryListView/index.js +3 -3
  30. package/dist/views/CategoryPaginationView/index.d.ts +1 -1
  31. package/dist/views/HomePaginationView/index.d.ts +1 -1
  32. package/dist/views/HomeView/index.d.ts +3 -1
  33. package/dist/views/HomeView/index.js +3 -2
  34. package/dist/views/LegalPageView/index.d.ts +3 -1
  35. package/dist/views/LegalPageView/index.js +3 -3
  36. package/dist/views/PostView/index.d.ts +5 -1
  37. package/dist/views/PostView/index.js +9 -4
  38. package/package.json +1 -1
@@ -8,9 +8,8 @@
8
8
  --spacing-xl: 3rem;
9
9
 
10
10
  /* Typography */
11
- --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", "Roboto",
12
- "Helvetica Neue", Arial, sans-serif;
13
- --font-display: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
11
+ --font-sans: Arial, sans-serif;
12
+ --font-display: Arial, sans-serif;
14
13
 
15
14
  /* Transitions */
16
15
  --transition: color 0.2s ease, background-color 0.2s ease,
@@ -20,5 +20,5 @@ type RootLayoutBaseProps = {
20
20
  headerLogo?: LogoProps;
21
21
  footerLogo?: LogoProps;
22
22
  };
23
- export declare function RootLayoutBase({ children, menuHeaderId, menuFooterId, siteName, wpApiUrl, wpUsername, wpAppPassword, homeHref, locale, headerLogo, footerLogo, }: RootLayoutBaseProps): import("react/jsx-runtime").JSX.Element;
23
+ export declare function RootLayoutBase({ children, menuHeaderId, menuFooterId, siteName, wpApiUrl, wpUsername, wpAppPassword, homeHref, locale, headerLogo, footerLogo, }: RootLayoutBaseProps): import("react").JSX.Element;
24
24
  export {};
@@ -8,13 +8,14 @@ async function getLegalParentId(baseApiUrl, lang, langId) {
8
8
  return null;
9
9
  const pagesApi = createPageEndpoints({ baseUrl: baseApiUrl });
10
10
  try {
11
- const parent = await pagesApi.list({
12
- slug: "legal",
13
- per_page: 1,
14
- _fields: ["id", "language", "taxonomies", "meta"],
11
+ const parent = await pagesApi.listAll({
12
+ parent: 0,
13
+ translation_key_v2: "legal",
14
+ _fields: ["id", "slug", "language", "taxonomies", "meta"],
15
15
  });
16
- const match = parent.items.find((p) => langId && getEntityLanguageId(p)?.toString() === langId.toString());
17
- return (match ?? parent.items[0])?.id ?? null;
16
+ const parentItems = parent.filter((item) => getTranslationKey(item) === "legal");
17
+ const match = parentItems.find((p) => langId && getEntityLanguageId(p)?.toString() === langId.toString());
18
+ return (match ?? parentItems[0])?.id ?? null;
18
19
  }
19
20
  catch (error) {
20
21
  console.error("Error obteniendo página padre legal:", error);
@@ -5,5 +5,5 @@ export type BreadcrumbItem = {
5
5
  type BreadcrumbsProps = {
6
6
  items: BreadcrumbItem[];
7
7
  };
8
- export declare function Breadcrumbs({ items }: BreadcrumbsProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function Breadcrumbs({ items }: BreadcrumbsProps): import("react").JSX.Element;
9
9
  export {};
@@ -1,5 +1,5 @@
1
1
  type JsonLdProps = {
2
2
  data?: unknown;
3
3
  };
4
- export declare function JsonLd({ data }: JsonLdProps): import("react/jsx-runtime").JSX.Element;
4
+ export declare function JsonLd({ data }: JsonLdProps): import("react").JSX.Element;
5
5
  export {};
@@ -0,0 +1,13 @@
1
+ export type LanguageLink = {
2
+ code: string;
3
+ label: string;
4
+ href: string;
5
+ current?: boolean;
6
+ };
7
+ type LanguageLinksProps = {
8
+ links?: LanguageLink[];
9
+ label?: string;
10
+ className?: string;
11
+ };
12
+ export declare function LanguageLinks({ links, label, className, }: LanguageLinksProps): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Link from "next/link";
3
+ import styles from "./index.module.css";
4
+ export function LanguageLinks({ links, label = "Available languages", className, }) {
5
+ const visibleLinks = links?.filter((link) => link.href) ?? [];
6
+ if (visibleLinks.length < 2)
7
+ return null;
8
+ return (_jsxs("nav", { "aria-label": label, className: [styles.languageLinks, className].filter(Boolean).join(" "), children: [_jsx("span", { className: styles.label, children: label }), _jsx("ul", { className: styles.list, children: visibleLinks.map((link) => (_jsx("li", { children: link.current ? (_jsx("span", { className: `${styles.link} ${styles.current}`, "aria-current": "page", lang: link.code, children: link.label })) : (_jsx(Link, { href: link.href, className: styles.link, hrefLang: link.code, children: link.label })) }, link.code))) })] }));
9
+ }
@@ -0,0 +1,78 @@
1
+ .languageLinks {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 0.6rem;
5
+ max-width: 100%;
6
+ margin: 1.25rem 0;
7
+ padding: 0.8rem 0.9rem;
8
+ border: 1px solid var(--border);
9
+ border-radius: 8px;
10
+ background: var(--background-alt);
11
+ color: var(--foreground-light);
12
+ font-size: 0.875rem;
13
+ }
14
+
15
+ .label {
16
+ flex: 0 0 auto;
17
+ font-weight: 650;
18
+ color: var(--foreground);
19
+ }
20
+
21
+ .list {
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ align-items: center;
25
+ gap: 0.4rem;
26
+ min-width: 0;
27
+ margin: 0;
28
+ padding: 0;
29
+ list-style: none;
30
+ }
31
+
32
+ .link {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ min-height: 2rem;
36
+ padding: 0.34rem 0.7rem;
37
+ border: 1px solid var(--border);
38
+ border-radius: 999px;
39
+ background: var(--background);
40
+ color: var(--foreground);
41
+ font-size: 0.82rem;
42
+ font-weight: 650;
43
+ line-height: 1;
44
+ text-decoration: none;
45
+ white-space: nowrap;
46
+ }
47
+
48
+ .link:hover {
49
+ color: var(--primary);
50
+ border-color: var(--primary);
51
+ }
52
+
53
+ .link[aria-current="page"] {
54
+ background: var(--foreground);
55
+ border-color: var(--foreground);
56
+ color: var(--background);
57
+ }
58
+
59
+ .current {
60
+ cursor: default;
61
+ }
62
+
63
+ .current:hover {
64
+ color: var(--background);
65
+ border-color: var(--foreground);
66
+ }
67
+
68
+ @media (max-width: 640px) {
69
+ .languageLinks {
70
+ align-items: flex-start;
71
+ flex-direction: column;
72
+ gap: 0.45rem;
73
+ }
74
+
75
+ .list {
76
+ width: 100%;
77
+ }
78
+ }
@@ -6,5 +6,5 @@ interface PaginatorProps {
6
6
  maxVisiblePages?: number;
7
7
  locale?: Locale;
8
8
  }
9
- export default function Paginator({ currentPage, totalPages, baseUrl, maxVisiblePages, locale, }: PaginatorProps): import("react/jsx-runtime").JSX.Element;
9
+ export default function Paginator({ currentPage, totalPages, baseUrl, maxVisiblePages, locale, }: PaginatorProps): import("react").JSX.Element;
10
10
  export {};
@@ -8,5 +8,5 @@ interface PostCardProps {
8
8
  dateLocale?: string;
9
9
  basePath?: string;
10
10
  }
11
- export default function PostCard({ post, categorySlug, locale, defaultLocale, dateLocale, basePath, }: PostCardProps): import("react/jsx-runtime").JSX.Element;
11
+ export default function PostCard({ post, categorySlug, locale, defaultLocale, dateLocale, basePath, }: PostCardProps): import("react").JSX.Element;
12
12
  export {};
@@ -4,3 +4,5 @@ export { default as Paginator } from "./Paginator/index.js";
4
4
  export { default as PostCard } from "./PostCard/index.js";
5
5
  export { Breadcrumbs } from "./Breadcrumbs/index.js";
6
6
  export { JsonLd } from "./JsonLd.js";
7
+ export { LanguageLinks } from "./LanguageLinks/index.js";
8
+ export type { LanguageLink } from "./LanguageLinks/index.js";
@@ -4,3 +4,4 @@ export { default as Paginator } from "./Paginator/index.js";
4
4
  export { default as PostCard } from "./PostCard/index.js";
5
5
  export { Breadcrumbs } from "./Breadcrumbs/index.js";
6
6
  export { JsonLd } from "./JsonLd.js";
7
+ export { LanguageLinks } from "./LanguageLinks/index.js";
@@ -10,5 +10,5 @@ type FooterProps = {
10
10
  locale?: string;
11
11
  logo?: LogoProps;
12
12
  };
13
- export default function Footer({ menuId, wpApiUrl, wpUsername, wpAppPassword, siteName, locale, logo, }: FooterProps): Promise<import("react/jsx-runtime").JSX.Element>;
13
+ export default function Footer({ menuId, wpApiUrl, wpUsername, wpAppPassword, siteName, locale, logo, }: FooterProps): Promise<import("react").JSX.Element>;
14
14
  export {};
@@ -17,5 +17,5 @@ type HeaderClientProps = {
17
17
  labels: HeaderLabels;
18
18
  logo?: LogoProps;
19
19
  };
20
- export default function HeaderClient({ menuLinks, siteName, homeHref, labels, logo, }: HeaderClientProps): import("react/jsx-runtime").JSX.Element;
20
+ export default function HeaderClient({ menuLinks, siteName, homeHref, labels, logo, }: HeaderClientProps): import("react").JSX.Element;
21
21
  export {};
@@ -11,5 +11,5 @@ type HeaderProps = {
11
11
  locale?: string;
12
12
  logo?: LogoProps;
13
13
  };
14
- export default function Header({ menuId, wpApiUrl, wpUsername, wpAppPassword, siteName, homeHref, locale, logo, }: HeaderProps): Promise<import("react/jsx-runtime").JSX.Element>;
14
+ export default function Header({ menuId, wpApiUrl, wpUsername, wpAppPassword, siteName, homeHref, locale, logo, }: HeaderProps): Promise<import("react").JSX.Element>;
15
15
  export {};
@@ -2,7 +2,7 @@ import type { ComponentPropsWithoutRef } from "react";
2
2
  type AlertProps = ComponentPropsWithoutRef<"div"> & {
3
3
  variant?: "default" | "destructive";
4
4
  };
5
- export declare function Alert({ className, variant, ...props }: AlertProps): import("react/jsx-runtime").JSX.Element;
6
- export declare function AlertTitle({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react/jsx-runtime").JSX.Element;
7
- export declare function AlertDescription({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react/jsx-runtime").JSX.Element;
5
+ export declare function Alert({ className, variant, ...props }: AlertProps): import("react").JSX.Element;
6
+ export declare function AlertTitle({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react").JSX.Element;
7
+ export declare function AlertDescription({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react").JSX.Element;
8
8
  export {};
@@ -1,3 +1,3 @@
1
1
  import type { ComponentPropsWithoutRef } from "react";
2
2
  export declare function badgeClassName(className?: string): string;
3
- export declare function Badge({ className, ...props }: ComponentPropsWithoutRef<"span">): import("react/jsx-runtime").JSX.Element;
3
+ export declare function Badge({ className, ...props }: ComponentPropsWithoutRef<"span">): import("react").JSX.Element;
@@ -1,8 +1,8 @@
1
1
  import Link from "next/link";
2
2
  import type { ComponentPropsWithoutRef } from "react";
3
- export declare function Breadcrumb({ className, ...props }: ComponentPropsWithoutRef<"nav">): import("react/jsx-runtime").JSX.Element;
4
- export declare function BreadcrumbList({ className, ...props }: ComponentPropsWithoutRef<"ol">): import("react/jsx-runtime").JSX.Element;
5
- export declare function BreadcrumbItem({ className, ...props }: ComponentPropsWithoutRef<"li">): import("react/jsx-runtime").JSX.Element;
6
- export declare function BreadcrumbLink({ className, ...props }: ComponentPropsWithoutRef<typeof Link>): import("react/jsx-runtime").JSX.Element;
7
- export declare function BreadcrumbPage({ className, ...props }: ComponentPropsWithoutRef<"span">): import("react/jsx-runtime").JSX.Element;
8
- export declare function BreadcrumbSeparator({ className, children, ...props }: ComponentPropsWithoutRef<"span">): import("react/jsx-runtime").JSX.Element;
3
+ export declare function Breadcrumb({ className, ...props }: ComponentPropsWithoutRef<"nav">): import("react").JSX.Element;
4
+ export declare function BreadcrumbList({ className, ...props }: ComponentPropsWithoutRef<"ol">): import("react").JSX.Element;
5
+ export declare function BreadcrumbItem({ className, ...props }: ComponentPropsWithoutRef<"li">): import("react").JSX.Element;
6
+ export declare function BreadcrumbLink({ className, ...props }: ComponentPropsWithoutRef<typeof Link>): import("react").JSX.Element;
7
+ export declare function BreadcrumbPage({ className, ...props }: ComponentPropsWithoutRef<"span">): import("react").JSX.Element;
8
+ export declare function BreadcrumbSeparator({ className, children, ...props }: ComponentPropsWithoutRef<"span">): import("react").JSX.Element;
@@ -3,5 +3,5 @@ type ButtonProps = ComponentPropsWithRef<"button"> & {
3
3
  variant?: "default" | "outline" | "ghost";
4
4
  size?: "default" | "sm" | "icon";
5
5
  };
6
- export declare function Button({ className, variant, size, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function Button({ className, variant, size, ...props }: ButtonProps): import("react").JSX.Element;
7
7
  export {};
@@ -4,8 +4,8 @@ type CardProps<T extends ElementType> = {
4
4
  size?: "default" | "sm";
5
5
  className?: string;
6
6
  } & Omit<ComponentPropsWithoutRef<T>, "as" | "className">;
7
- export declare function Card<T extends ElementType = "div">({ as, size, className, ...props }: CardProps<T>): import("react/jsx-runtime").JSX.Element;
8
- export declare function CardHeader({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react/jsx-runtime").JSX.Element;
9
- export declare function CardContent({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react/jsx-runtime").JSX.Element;
10
- export declare function CardFooter({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react/jsx-runtime").JSX.Element;
7
+ export declare function Card<T extends ElementType = "div">({ as, size, className, ...props }: CardProps<T>): import("react").JSX.Element;
8
+ export declare function CardHeader({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react").JSX.Element;
9
+ export declare function CardContent({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react").JSX.Element;
10
+ export declare function CardFooter({ className, ...props }: ComponentPropsWithoutRef<"div">): import("react").JSX.Element;
11
11
  export {};
@@ -1,14 +1,14 @@
1
1
  import Link from "next/link";
2
2
  import type { ComponentPropsWithoutRef } from "react";
3
- export declare function Pagination({ className, ...props }: ComponentPropsWithoutRef<"nav">): import("react/jsx-runtime").JSX.Element;
4
- export declare function PaginationContent({ className, ...props }: ComponentPropsWithoutRef<"ul">): import("react/jsx-runtime").JSX.Element;
5
- export declare function PaginationItem(props: ComponentPropsWithoutRef<"li">): import("react/jsx-runtime").JSX.Element;
3
+ export declare function Pagination({ className, ...props }: ComponentPropsWithoutRef<"nav">): import("react").JSX.Element;
4
+ export declare function PaginationContent({ className, ...props }: ComponentPropsWithoutRef<"ul">): import("react").JSX.Element;
5
+ export declare function PaginationItem(props: ComponentPropsWithoutRef<"li">): import("react").JSX.Element;
6
6
  type PaginationLinkProps = ComponentPropsWithoutRef<typeof Link> & {
7
7
  isActive?: boolean;
8
8
  size?: "default" | "icon";
9
9
  };
10
- export declare function PaginationLink({ className, isActive, size, ...props }: PaginationLinkProps): import("react/jsx-runtime").JSX.Element;
11
- export declare function PaginationPrevious({ children, className, ...props }: Omit<PaginationLinkProps, "size">): import("react/jsx-runtime").JSX.Element;
12
- export declare function PaginationNext({ children, className, ...props }: Omit<PaginationLinkProps, "size">): import("react/jsx-runtime").JSX.Element;
13
- export declare function PaginationEllipsis({ className, ...props }: ComponentPropsWithoutRef<"span">): import("react/jsx-runtime").JSX.Element;
10
+ export declare function PaginationLink({ className, isActive, size, ...props }: PaginationLinkProps): import("react").JSX.Element;
11
+ export declare function PaginationPrevious({ children, className, ...props }: Omit<PaginationLinkProps, "size">): import("react").JSX.Element;
12
+ export declare function PaginationNext({ children, className, ...props }: Omit<PaginationLinkProps, "size">): import("react").JSX.Element;
13
+ export declare function PaginationEllipsis({ className, ...props }: ComponentPropsWithoutRef<"span">): import("react").JSX.Element;
14
14
  export {};
@@ -1,5 +1,7 @@
1
1
  import { type SiteLanguage, type SiteLanguageInput } from "./language.js";
2
+ import type { LanguageLink } from "../components/index.js";
2
3
  export type LanguageConfig = SiteLanguageInput;
4
+ export declare function getHreflangCode(language: Pick<SiteLanguageInput, "code">): string;
3
5
  export declare function getTranslationKey(entity: unknown): string | undefined;
4
6
  export declare function getEntityLanguageId(entity: unknown): string | undefined;
5
7
  type BuildUrlParams<T> = {
@@ -31,4 +33,9 @@ export declare function buildAbsoluteLangUrl({ basePath, path, siteUrl, }: {
31
33
  siteUrl?: string;
32
34
  }): string;
33
35
  export declare function withXDefault(languages?: Record<string, string>): Record<string, string> | undefined;
36
+ export declare function buildLanguageLinks({ languages, alternates, currentCode, }: {
37
+ languages: LanguageConfig[];
38
+ alternates?: Record<string, string>;
39
+ currentCode?: string;
40
+ }): LanguageLink[];
34
41
  export {};
@@ -1,6 +1,19 @@
1
1
  import { getAbsoluteUrl } from "./routing.js";
2
2
  import { normalizeLanguages, shouldRenderLanguageAlternates, } from "./language.js";
3
+ const languageLabels = {
4
+ en: "English",
5
+ es: "Español",
6
+ fr: "Français",
7
+ de: "Deutsch",
8
+ "pt-br": "Português",
9
+ };
3
10
  const normalizeBasePath = (basePath) => basePath.endsWith("/") ? basePath : `${basePath}/`;
11
+ export function getHreflangCode(language) {
12
+ const [languageCode, regionCode] = language.code.split("-");
13
+ if (!regionCode)
14
+ return languageCode.toLowerCase();
15
+ return `${languageCode.toLowerCase()}-${regionCode.toUpperCase()}`;
16
+ }
4
17
  export function getTranslationKey(entity) {
5
18
  const meta = entity?.meta;
6
19
  const value = meta?.translation_key_v2;
@@ -33,7 +46,7 @@ export async function buildLanguageAlternatesFromTranslations({ translations, la
33
46
  const url = buildUrl({ entity: match, langCfg });
34
47
  if (!url)
35
48
  continue;
36
- alternates[langCfg.code] = url;
49
+ alternates[getHreflangCode(langCfg)] = url;
37
50
  }
38
51
  return Object.keys(alternates).length > 1 ? alternates : undefined;
39
52
  }
@@ -68,7 +81,7 @@ export async function buildLanguageAlternatesPerLanguage({ languages, fetcher, b
68
81
  const url = buildUrl({ entity, langCfg });
69
82
  if (!url)
70
83
  continue;
71
- alternates[langCfg.code] = url;
84
+ alternates[getHreflangCode(langCfg)] = url;
72
85
  }
73
86
  catch (error) {
74
87
  console.error("Error building hreflang for lang", langCfg.code, error);
@@ -98,3 +111,23 @@ export function withXDefault(languages) {
98
111
  : { ...languages };
99
112
  return Object.keys(result).length > 1 ? result : undefined;
100
113
  }
114
+ export function buildLanguageLinks({ languages, alternates, currentCode, }) {
115
+ if (!alternates)
116
+ return [];
117
+ return normalizeLanguages(languages)
118
+ .map((language) => {
119
+ const hreflang = getHreflangCode(language);
120
+ const href = alternates[hreflang];
121
+ if (!href)
122
+ return null;
123
+ return {
124
+ code: hreflang,
125
+ label: languageLabels[language.code] || language.locale || language.code,
126
+ href,
127
+ current: language.code === currentCode ||
128
+ language.locale === currentCode ||
129
+ hreflang === currentCode,
130
+ };
131
+ })
132
+ .filter((link) => Boolean(link));
133
+ }
@@ -1,6 +1,8 @@
1
- /**
2
- * Remove HTML tags from a string.
3
- */
4
1
  export declare function stripTags(html?: string): string | undefined;
5
2
  export declare function decodeHtmlEntities(value?: string): string | undefined;
6
3
  export declare function toPlainText(html?: string): string | undefined;
4
+ export declare function rewritePostContentLinks(html: string, opts?: {
5
+ siteUrl?: string;
6
+ wpApiUrl?: string;
7
+ basePath?: string;
8
+ }): string;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Remove HTML tags from a string.
3
3
  */
4
+ import { ensureTrailingSlash, getSiteUrl } from "./routing.js";
4
5
  export function stripTags(html) {
5
6
  if (!html)
6
7
  return undefined;
@@ -31,3 +32,50 @@ export function toPlainText(html) {
31
32
  const decoded = decodeHtmlEntities(stripped);
32
33
  return decoded?.replace(/\s+/g, " ").trim() || undefined;
33
34
  }
35
+ const getOrigin = (url) => {
36
+ if (!url)
37
+ return null;
38
+ try {
39
+ return new URL(url).origin;
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ };
45
+ const getWpOrigin = (wpApiUrl) => {
46
+ if (!wpApiUrl)
47
+ return null;
48
+ return getOrigin(wpApiUrl.replace(/\/wp-json\/?$/, ""));
49
+ };
50
+ const escapeHtmlAttribute = (value) => value
51
+ .replace(/&/g, "&amp;")
52
+ .replace(/"/g, "&quot;")
53
+ .replace(/</g, "&lt;")
54
+ .replace(/>/g, "&gt;");
55
+ const normalizePathWithBase = (basePath, pathname) => {
56
+ const normalizedBase = ensureTrailingSlash(basePath || "/");
57
+ const normalizedPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
58
+ return ensureTrailingSlash(`${normalizedBase}${normalizedPath}`.replace(/\/+/g, "/"));
59
+ };
60
+ export function rewritePostContentLinks(html, opts = {}) {
61
+ const sourceOrigin = getWpOrigin(opts.wpApiUrl);
62
+ const targetOrigin = getOrigin(getSiteUrl(opts.siteUrl));
63
+ if (!sourceOrigin || !targetOrigin || sourceOrigin === targetOrigin) {
64
+ return html;
65
+ }
66
+ return html.replace(/\bhref\s*=\s*(["'])(.*?)\1/gi, (match, quote, rawHref) => {
67
+ const decodedHref = decodeHtmlEntities(rawHref) || rawHref;
68
+ let parsed;
69
+ try {
70
+ parsed = new URL(decodedHref);
71
+ }
72
+ catch {
73
+ return match;
74
+ }
75
+ if (parsed.origin !== sourceOrigin)
76
+ return match;
77
+ const path = normalizePathWithBase(opts.basePath || "/", parsed.pathname);
78
+ const rewritten = `${targetOrigin}${path}${parsed.search}${parsed.hash}`;
79
+ return `href=${quote}${escapeHtmlAttribute(rewritten)}${quote}`;
80
+ });
81
+ }
@@ -3,6 +3,8 @@ export type SiteLanguageInput = {
3
3
  basePath?: string;
4
4
  langId?: string;
5
5
  wpLang?: string;
6
+ menuHeaderId?: string;
7
+ menuFooterId?: string;
6
8
  locale?: string;
7
9
  dateLocale?: string;
8
10
  isDefault?: boolean;
@@ -1,21 +1,42 @@
1
1
  import { createPageEndpoints, createPostsEndpoints } from "wpjsapi-lib";
2
2
  import { createWpClient, isValidWpApiUrl } from "../api/wpClient.js";
3
3
  import { listPostsWithEmbeds } from "../api/posts.js";
4
- import { getCategoryPaginationStaticParams } from "../base/index.js";
5
- import { getHomePaginationStaticParams } from "../base/home/pagination.js";
4
+ import { getCategoryPaginationStaticParams, getCategoryTotalPages, } from "../base/index.js";
5
+ import { getHomePaginationStaticParams, getHomeTotalPages, } from "../base/home/pagination.js";
6
6
  import { getAbsoluteUrl, ensureTrailingSlash } from "./routing.js";
7
7
  import { isYoastNoindex } from "./seo.js";
8
- import { getTranslationKey, getEntityLanguageId, withXDefault, } from "./hreflang.js";
8
+ import { getHreflangCode, getTranslationKey, getEntityLanguageId, withXDefault, } from "./hreflang.js";
9
9
  import { normalizeLanguages, shouldRenderLanguageAlternates, } from "./language.js";
10
10
  const resolveWpApiUrl = (cfg) => cfg.wpApiUrl ?? process.env.WP_API_URL ?? "";
11
11
  const resolveSiteUrl = (cfg) => cfg.siteUrl ?? process.env.NEXT_PUBLIC_SITE_URL;
12
12
  const buildHomeAlternates = (cfg) => shouldRenderLanguageAlternates(cfg.languages)
13
13
  ? withXDefault(normalizeLanguages(cfg.languages).reduce((acc, langCfg) => {
14
14
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
15
- acc[langCfg.code] = getAbsoluteUrl(basePath, resolveSiteUrl(cfg));
15
+ acc[getHreflangCode(langCfg)] = getAbsoluteUrl(basePath, resolveSiteUrl(cfg));
16
16
  return acc;
17
17
  }, {}))
18
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
+ }
19
40
  const escapeXml = (value) => value
20
41
  .replace(/&/g, "&amp;")
21
42
  .replace(/</g, "&lt;")
@@ -23,6 +44,9 @@ const escapeXml = (value) => value
23
44
  .replace(/"/g, "&quot;")
24
45
  .replace(/'/g, "&apos;");
25
46
  const renderAlternates = (alternates) => {
47
+ const languageKeys = Object.keys(alternates ?? {}).filter((key) => key !== "x-default");
48
+ if (languageKeys.length < 2)
49
+ return undefined;
26
50
  const normalizedAlternates = withXDefault(alternates);
27
51
  if (!normalizedAlternates)
28
52
  return undefined;
@@ -130,6 +154,7 @@ export async function getHomePaginationEntries(cfg) {
130
154
  return {
131
155
  url,
132
156
  lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
157
+ alternates: await buildHomePaginationAlternates(cfg, Number(p.page)),
133
158
  };
134
159
  });
135
160
  entries.push(...pageEntries);
@@ -144,6 +169,7 @@ export async function getCategoryEntries(cfg) {
144
169
  const siteUrl = resolveSiteUrl(cfg);
145
170
  if (!isValidWpApiUrl(wpApiUrl))
146
171
  return entries;
172
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
147
173
  const categoriesPerLang = {};
148
174
  const categoryAlternates = {};
149
175
  for (const langCfg of languages) {
@@ -156,6 +182,8 @@ export async function getCategoryEntries(cfg) {
156
182
  categoriesPerLang[langCfg.code] = indexableCategories;
157
183
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
158
184
  for (const cat of indexableCategories) {
185
+ if (!renderAlternatesForLanguages)
186
+ continue;
159
187
  const translationKey = getTranslationKey(cat);
160
188
  if (!translationKey)
161
189
  continue;
@@ -163,7 +191,7 @@ export async function getCategoryEntries(cfg) {
163
191
  if (!categoryAlternates[translationKey]) {
164
192
  categoryAlternates[translationKey] = {};
165
193
  }
166
- categoryAlternates[translationKey][langCfg.code] = url;
194
+ categoryAlternates[translationKey][getHreflangCode(langCfg)] = url;
167
195
  }
168
196
  }
169
197
  for (const langCfg of languages) {
@@ -184,6 +212,8 @@ export async function getCategoryEntries(cfg) {
184
212
  const lastmod = latest?.modified ||
185
213
  latest?.date;
186
214
  const alt = (() => {
215
+ if (!renderAlternatesForLanguages)
216
+ return undefined;
187
217
  const key = getTranslationKey(cat);
188
218
  if (!key)
189
219
  return undefined;
@@ -208,15 +238,43 @@ export async function getCategoryPaginationEntries(cfg) {
208
238
  const siteUrl = resolveSiteUrl(cfg);
209
239
  if (!isValidWpApiUrl(wpApiUrl))
210
240
  return entries;
241
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
211
242
  const catIdsBySlugByLang = {};
243
+ const catsBySlugByLang = {};
244
+ const alternatesByTranslationPage = {};
212
245
  for (const langCfg of languages) {
213
246
  const { categoriesApi } = createWpClient({ baseUrl: wpApiUrl });
214
247
  const categories = await categoriesApi.listAll({
215
248
  ...getWpLangParam(langCfg),
216
- _fields: ["id", "slug", "yoast_head_json"],
249
+ _fields: ["id", "slug", "meta", "yoast_head_json"],
217
250
  });
218
251
  const indexableCategories = categories.filter((cat) => !isNoindexEntity(cat));
219
252
  catIdsBySlugByLang[langCfg.code] = Object.fromEntries(indexableCategories.map((c) => [c.slug, c.id]));
253
+ catsBySlugByLang[langCfg.code] = Object.fromEntries(indexableCategories.map((c) => [c.slug, c]));
254
+ const basePath = ensureTrailingSlash(langCfg.basePath || "/");
255
+ for (const category of indexableCategories) {
256
+ if (!renderAlternatesForLanguages)
257
+ continue;
258
+ const translationKey = getTranslationKey(category);
259
+ if (!translationKey)
260
+ continue;
261
+ const totalPages = await getCategoryTotalPages({
262
+ baseApiUrl: wpApiUrl,
263
+ categoryId: category.id,
264
+ perPage: runtimeConfig.postsPerPagePagination,
265
+ langId: langCfg.langId,
266
+ });
267
+ for (let page = 2; page <= totalPages; page++) {
268
+ const pageKey = String(page);
269
+ if (!alternatesByTranslationPage[translationKey]) {
270
+ alternatesByTranslationPage[translationKey] = {};
271
+ }
272
+ if (!alternatesByTranslationPage[translationKey][pageKey]) {
273
+ alternatesByTranslationPage[translationKey][pageKey] = {};
274
+ }
275
+ alternatesByTranslationPage[translationKey][pageKey][getHreflangCode(langCfg)] = getAbsoluteUrl(`${basePath}${category.slug}/page/${page}/`, siteUrl);
276
+ }
277
+ }
220
278
  }
221
279
  for (const langCfg of languages) {
222
280
  const catPages = await getCategoryPaginationStaticParams({
@@ -231,6 +289,7 @@ export async function getCategoryPaginationEntries(cfg) {
231
289
  const catId = catIdsBySlugByLang[langCfg.code]?.[page.category];
232
290
  if (!catId)
233
291
  return null;
292
+ const category = catsBySlugByLang[langCfg.code]?.[page.category];
234
293
  let lastmod;
235
294
  const pagePosts = await listPostsWithEmbeds({
236
295
  baseApiUrl: wpApiUrl,
@@ -248,6 +307,9 @@ export async function getCategoryPaginationEntries(cfg) {
248
307
  return {
249
308
  url,
250
309
  lastmod: lastmod ? new Date(lastmod).toISOString() : undefined,
310
+ alternates: renderAlternatesForLanguages && category
311
+ ? withXDefault(alternatesByTranslationPage[getTranslationKey(category) || ""]?.[page.page])
312
+ : undefined,
251
313
  };
252
314
  });
253
315
  entries.push(...pageEntries.filter(Boolean));
@@ -264,6 +326,7 @@ export async function getPostEntriesForLang(langCode, cfg) {
264
326
  return [];
265
327
  const siteUrl = resolveSiteUrl(cfg);
266
328
  const postsApi = createPostsEndpoints({ baseUrl: wpApiUrl });
329
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
267
330
  const languageFilter = langCfg.langId
268
331
  ? { taxonomies: { language: [langCfg.langId] } }
269
332
  : {};
@@ -290,6 +353,8 @@ export async function getPostEntriesForLang(langCode, cfg) {
290
353
  });
291
354
  for (const { language: l, base, posts: filteredLangPosts } of postsByLanguage) {
292
355
  for (const p of filteredLangPosts) {
356
+ if (!renderAlternatesForLanguages)
357
+ continue;
293
358
  const translationKey = getTranslationKey(p);
294
359
  if (!translationKey)
295
360
  continue;
@@ -300,7 +365,7 @@ export async function getPostEntriesForLang(langCode, cfg) {
300
365
  const url = getAbsoluteUrl(`${base}${categorySlug}/${p.slug}/`, siteUrl);
301
366
  if (!postAlternates[translationKey])
302
367
  postAlternates[translationKey] = {};
303
- postAlternates[translationKey][l.code] = url;
368
+ postAlternates[translationKey][getHreflangCode(l)] = url;
304
369
  }
305
370
  }
306
371
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
@@ -314,7 +379,7 @@ export async function getPostEntriesForLang(langCode, cfg) {
314
379
  const lastmod = p.modified ||
315
380
  p.date;
316
381
  const translationKey = getTranslationKey(p);
317
- const alts = translationKey
382
+ const alts = renderAlternatesForLanguages && translationKey
318
383
  ? withXDefault(postAlternates[translationKey])
319
384
  : undefined;
320
385
  return {
@@ -332,6 +397,7 @@ export async function getLegalEntries(cfg) {
332
397
  const wpApiUrl = resolveWpApiUrl(cfg);
333
398
  if (!isValidWpApiUrl(wpApiUrl))
334
399
  return entries;
400
+ const renderAlternatesForLanguages = shouldRenderLanguageAlternates(cfg.languages);
335
401
  const pagesApi = createPageEndpoints({ baseUrl: wpApiUrl });
336
402
  const pagesPerLang = {};
337
403
  const alternatesByKey = {};
@@ -357,18 +423,22 @@ export async function getLegalEntries(cfg) {
357
423
  "yoast_head_json",
358
424
  ],
359
425
  });
360
- const filtered = pages.filter((p) => getEntityLanguageId(p)?.toString() === langCfg.langId?.toString());
426
+ const filtered = langCfg.langId
427
+ ? pages.filter((p) => getEntityLanguageId(p)?.toString() === langCfg.langId?.toString())
428
+ : pages;
361
429
  const indexablePages = filtered.filter((page) => !isNoindexEntity(page));
362
430
  pagesPerLang[langCfg.code] = indexablePages;
363
431
  const basePath = ensureTrailingSlash(langCfg.basePath || "/");
364
432
  for (const p of indexablePages) {
433
+ if (!renderAlternatesForLanguages)
434
+ continue;
365
435
  const translationKey = getTranslationKey(p);
366
436
  if (!translationKey)
367
437
  continue;
368
438
  const url = getAbsoluteUrl(`${basePath}legal/${p.slug}/`, siteUrl);
369
439
  if (!alternatesByKey[translationKey])
370
440
  alternatesByKey[translationKey] = {};
371
- alternatesByKey[translationKey][langCfg.code] = url;
441
+ alternatesByKey[translationKey][getHreflangCode(langCfg)] = url;
372
442
  }
373
443
  }
374
444
  for (const langCfg of languages) {
@@ -378,7 +448,7 @@ export async function getLegalEntries(cfg) {
378
448
  const url = getAbsoluteUrl(`${basePath}legal/${p.slug}/`, siteUrl);
379
449
  const lastmod = p.modified || p.date;
380
450
  const translationKey = getTranslationKey(p);
381
- const alts = translationKey
451
+ const alts = renderAlternatesForLanguages && translationKey
382
452
  ? withXDefault(alternatesByKey[translationKey])
383
453
  : undefined;
384
454
  entries.push({
@@ -1,5 +1,6 @@
1
1
  import { type WPCategory, type WPPost } from "wpjsapi-lib";
2
2
  import { type Locale } from "../../utils/index.js";
3
+ import { type LanguageLink } from "../../components/index.js";
3
4
  type CategoryListViewProps = {
4
5
  category: WPCategory;
5
6
  posts: WPPost[];
@@ -9,6 +10,7 @@ type CategoryListViewProps = {
9
10
  linkBasePath?: string;
10
11
  locale?: Locale;
11
12
  dateLocale?: string;
13
+ languageLinks?: LanguageLink[];
12
14
  };
13
- export declare function CategoryListView({ category, posts, totalPages, baseUrl, homeHref, linkBasePath, locale, dateLocale, }: CategoryListViewProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function CategoryListView({ category, posts, totalPages, baseUrl, homeHref, linkBasePath, locale, dateLocale, languageLinks, }: CategoryListViewProps): import("react").JSX.Element;
14
16
  export {};
@@ -3,9 +3,9 @@ import Paginator from "../../components/Paginator/index.js";
3
3
  import PostCard from "../../components/PostCard/index.js";
4
4
  import styles from "./index.module.css";
5
5
  import { getCountLabel, getEmptyLabel, getPaginatorBase } from "./index.utils.js";
6
- import { Breadcrumbs, JsonLd } from "../../components/index.js";
6
+ import { Breadcrumbs, JsonLd, LanguageLinks } from "../../components/index.js";
7
7
  import { getTranslator, getYoastSchema } from "../../utils/index.js";
8
- export function CategoryListView({ category, posts, totalPages, baseUrl, homeHref = "/", linkBasePath, locale, dateLocale = "en-US", }) {
8
+ export function CategoryListView({ category, posts, totalPages, baseUrl, homeHref = "/", linkBasePath, locale, dateLocale = "en-US", languageLinks, }) {
9
9
  const paginatorBase = getPaginatorBase(category, baseUrl);
10
10
  const countLabel = getCountLabel(category, locale);
11
11
  const emptyLabel = getEmptyLabel(locale);
@@ -13,5 +13,5 @@ export function CategoryListView({ category, posts, totalPages, baseUrl, homeHre
13
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
- ] }), _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 })] }))] }));
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 }), _jsx(LanguageLinks, { links: languageLinks })] }))] }));
17
17
  }
@@ -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 {};
@@ -10,5 +10,5 @@ type HomePaginationViewProps = {
10
10
  locale?: Locale;
11
11
  dateLocale?: string;
12
12
  };
13
- export declare function HomePaginationView({ posts, currentPage, totalPages, error, baseUrl, linkBasePath, locale, dateLocale, }: HomePaginationViewProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function HomePaginationView({ posts, currentPage, totalPages, error, baseUrl, linkBasePath, locale, dateLocale, }: HomePaginationViewProps): import("react").JSX.Element;
14
14
  export {};
@@ -1,5 +1,6 @@
1
1
  import { type WPPost } from "wpjsapi-lib";
2
2
  import { type Locale } from "../../utils/index.js";
3
+ import { type LanguageLink } from "../../components/index.js";
3
4
  type HomeViewProps = {
4
5
  featuredPost?: WPPost;
5
6
  regularPosts: WPPost[];
@@ -9,6 +10,7 @@ type HomeViewProps = {
9
10
  linkBasePath?: string;
10
11
  locale?: Locale;
11
12
  dateLocale?: string;
13
+ languageLinks?: LanguageLink[];
12
14
  };
13
- export declare function HomeView({ featuredPost, regularPosts, error, totalPages, baseUrl, linkBasePath, locale, dateLocale, }: HomeViewProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function HomeView({ featuredPost, regularPosts, error, totalPages, baseUrl, linkBasePath, locale, dateLocale, languageLinks, }: HomeViewProps): import("react").JSX.Element;
14
16
  export {};
@@ -6,11 +6,12 @@ import PostCard from "../../components/PostCard/index.js";
6
6
  import styles from "./index.module.css";
7
7
  import { buildFeaturedViewModel, getHomeCopy } from "./index.utils.js";
8
8
  import { Alert, AlertDescription, AlertTitle, badgeClassName, } from "../../components/ui/index.js";
9
+ import { LanguageLinks } from "../../components/index.js";
9
10
  function truncateLabel(label, max = 20) {
10
11
  const trimmed = label.trim();
11
12
  return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
12
13
  }
13
- export function HomeView({ featuredPost, regularPosts, error, totalPages, baseUrl = "", linkBasePath, locale, dateLocale = "en-US", }) {
14
+ export function HomeView({ featuredPost, regularPosts, error, totalPages, baseUrl = "", linkBasePath, locale, dateLocale = "en-US", languageLinks, }) {
14
15
  const copy = getHomeCopy(locale);
15
16
  const featuredModel = featuredPost
16
17
  ? buildFeaturedViewModel(featuredPost, locale, dateLocale, linkBasePath)
@@ -22,5 +23,5 @@ export function HomeView({ featuredPost, regularPosts, error, totalPages, baseUr
22
23
  __html: featuredModel.titleHtml,
23
24
  }, className: styles.featuredTitle }) }), _jsx("div", { dangerouslySetInnerHTML: {
24
25
  __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 })] })) }));
26
+ }, 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 }), _jsx(LanguageLinks, { links: languageLinks })] })) }));
26
27
  }
@@ -1,9 +1,11 @@
1
1
  import { type WPPage } from "wpjsapi-lib";
2
+ import { type LanguageLink } from "../../components/index.js";
2
3
  type LegalPageViewProps = {
3
4
  page: WPPage;
4
5
  dateLocale?: string;
5
6
  homeHref: string;
6
7
  priorityImage?: boolean;
8
+ languageLinks?: LanguageLink[];
7
9
  };
8
- export declare function LegalPageView({ page, homeHref, priorityImage, }: LegalPageViewProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function LegalPageView({ page, homeHref, priorityImage, languageLinks, }: LegalPageViewProps): import("react").JSX.Element;
9
11
  export {};
@@ -2,11 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Image from "next/image";
3
3
  import styles from "./index.module.css";
4
4
  import { getLegalContent, getLegalImage } from "./index.utils.js";
5
- import { Breadcrumbs, JsonLd } from "../../components/index.js";
5
+ import { Breadcrumbs, JsonLd, LanguageLinks } from "../../components/index.js";
6
6
  import { getYoastSchema, stripTags } from "../../utils/index.js";
7
- export function LegalPageView({ page, homeHref, priorityImage = true, }) {
7
+ export function LegalPageView({ page, homeHref, priorityImage = true, languageLinks, }) {
8
8
  const featuredImage = getLegalImage(page);
9
9
  const { titleHtml, contentHtml } = getLegalContent(page);
10
10
  const titleText = stripTags(titleHtml) || titleHtml;
11
- return (_jsxs("article", { className: styles.article, children: [_jsx(JsonLd, { data: getYoastSchema(page) }), _jsx(Breadcrumbs, { items: [{ label: "Home", href: homeHref }, { label: titleText }] }), featuredImage && (_jsx("div", { className: styles.imageWrapper, children: _jsx(Image, { src: featuredImage.src, alt: featuredImage.alt, width: featuredImage.width, height: featuredImage.height, className: styles.image, priority: priorityImage }) })), _jsx("h1", { dangerouslySetInnerHTML: { __html: titleHtml }, className: styles.title }), _jsx("div", { dangerouslySetInnerHTML: { __html: contentHtml }, className: `post-content ${styles.content}` })] }));
11
+ return (_jsxs("article", { className: styles.article, children: [_jsx(JsonLd, { data: getYoastSchema(page) }), _jsx(Breadcrumbs, { items: [{ label: "Home", href: homeHref }, { label: titleText }] }), featuredImage && (_jsx("div", { className: styles.imageWrapper, children: _jsx(Image, { src: featuredImage.src, alt: featuredImage.alt, width: featuredImage.width, height: featuredImage.height, className: styles.image, priority: priorityImage }) })), _jsx("h1", { dangerouslySetInnerHTML: { __html: titleHtml }, className: styles.title }), _jsx(LanguageLinks, { links: languageLinks }), _jsx("div", { dangerouslySetInnerHTML: { __html: contentHtml }, className: `post-content ${styles.content}` })] }));
12
12
  }
@@ -1,5 +1,6 @@
1
1
  import { type WPPost } from "wpjsapi-lib";
2
2
  import { type Locale } from "../../utils/index.js";
3
+ import { type LanguageLink } from "../../components/index.js";
3
4
  type PostViewProps = {
4
5
  post: WPPost;
5
6
  dateLocale?: string;
@@ -8,6 +9,9 @@ type PostViewProps = {
8
9
  authorDescription?: string | null;
9
10
  disclaimer?: string;
10
11
  priorityImage?: boolean;
12
+ siteUrl?: string;
13
+ wpApiUrl?: string;
14
+ languageLinks?: LanguageLink[];
11
15
  };
12
- export declare function PostView({ post, dateLocale, categoryBasePath, locale, authorDescription, disclaimer, priorityImage, }: PostViewProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function PostView({ post, dateLocale, categoryBasePath, locale, authorDescription, disclaimer, priorityImage, siteUrl, wpApiUrl, languageLinks, }: PostViewProps): import("react").JSX.Element;
13
17
  export {};
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Image from "next/image";
3
3
  import Link from "next/link";
4
4
  import styles from "./index.module.css";
5
- import { getTranslator, getYoastSchema } from "../../utils/index.js";
6
- import { Breadcrumbs, JsonLd } from "../../components/index.js";
5
+ import { getTranslator, getYoastSchema, rewritePostContentLinks, } from "../../utils/index.js";
6
+ import { Breadcrumbs, JsonLd, LanguageLinks } from "../../components/index.js";
7
7
  import { buildCategoryUrl, formatPublishedDate, getAuthor, getFeaturedImage, getPrimaryCategory, } from "./index.utils.js";
8
8
  import { stripTags } from "../../utils/index.js";
9
9
  import { Alert, badgeClassName, Card, CardContent } from "../../components/ui/index.js";
@@ -11,7 +11,7 @@ function truncateLabel(label, max = 20) {
11
11
  const trimmed = label.trim();
12
12
  return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
13
13
  }
14
- export function PostView({ post, dateLocale = "en-US", categoryBasePath = "/", locale, authorDescription, disclaimer, priorityImage = true, }) {
14
+ export function PostView({ post, dateLocale = "en-US", categoryBasePath = "/", locale, authorDescription, disclaimer, priorityImage = true, siteUrl, wpApiUrl, languageLinks, }) {
15
15
  const t = getTranslator(locale);
16
16
  const author = getAuthor(post);
17
17
  const authorBio = authorDescription ?? author?.description;
@@ -19,6 +19,11 @@ export function PostView({ post, dateLocale = "en-US", categoryBasePath = "/", l
19
19
  const categoryUrl = buildCategoryUrl(categoryBasePath, primaryCategory?.slug);
20
20
  const featuredImage = getFeaturedImage(post, locale);
21
21
  const publishedDate = formatPublishedDate(post.date, dateLocale);
22
+ const contentHtml = rewritePostContentLinks(post.content.rendered, {
23
+ siteUrl,
24
+ wpApiUrl,
25
+ basePath: categoryBasePath,
26
+ });
22
27
  const breadcrumbs = primaryCategory && categoryUrl
23
28
  ? [
24
29
  { label: "Home", href: categoryBasePath },
@@ -29,5 +34,5 @@ export function PostView({ post, dateLocale = "en-US", categoryBasePath = "/", l
29
34
  { label: "Home", href: categoryBasePath },
30
35
  { label: stripTags(post.title.rendered) || post.title.rendered },
31
36
  ];
32
- return (_jsxs("div", { className: styles.container, children: [_jsx(JsonLd, { data: getYoastSchema(post) }), _jsxs("article", { className: styles.article, children: [_jsx(Breadcrumbs, { items: breadcrumbs }), featuredImage && (_jsxs("figure", { className: styles.figure, children: [_jsx(Image, { src: featuredImage.src, alt: featuredImage.alt, fill: true, className: styles.image, priority: priorityImage, sizes: "(max-width: 1200px) 100vw, 1200px" }), _jsx("div", { className: styles.heroOverlay, "aria-hidden": "true" }), primaryCategory && (_jsx("div", { className: styles.badgeOverlay, children: categoryUrl ? (_jsx(Link, { href: categoryUrl, className: badgeClassName(), title: primaryCategory.name, children: truncateLabel(primaryCategory.name, 20) })) : (_jsx("span", { className: badgeClassName(), title: primaryCategory.name, children: truncateLabel(primaryCategory.name, 20) })) })), _jsx("h1", { dangerouslySetInnerHTML: { __html: post.title.rendered }, className: `${styles.title} ${styles.heroTitle}` })] })), !featuredImage && (_jsx("header", { className: styles.postHeader, children: _jsx("h1", { dangerouslySetInnerHTML: { __html: post.title.rendered }, className: styles.title }) })), _jsxs("div", { className: styles.meta, children: [author && (_jsx("span", { className: styles.authorLabel, children: t("post.authorPrefix", { name: author.name }) })), _jsx("span", { children: "\u2022" }), _jsx("time", { dateTime: post.date, children: publishedDate })] }), _jsx("div", { dangerouslySetInnerHTML: { __html: post.content.rendered }, className: `post-content ${styles.content}` }), disclaimer && (_jsx(Alert, { className: styles.disclaimer, role: "note", children: disclaimer })), author && (_jsx(Card, { as: "aside", className: styles.authorCard, size: "sm", children: _jsx(CardContent, { children: _jsxs("address", { className: styles.authorAddress, children: [_jsx("strong", { className: styles.authorName, children: author.name }), authorBio && _jsx("p", { className: styles.authorBio, children: authorBio })] }) }) }))] })] }));
37
+ return (_jsxs("div", { className: styles.container, children: [_jsx(JsonLd, { data: getYoastSchema(post) }), _jsxs("article", { className: styles.article, children: [_jsx(Breadcrumbs, { items: breadcrumbs }), featuredImage && (_jsxs("figure", { className: styles.figure, children: [_jsx(Image, { src: featuredImage.src, alt: featuredImage.alt, fill: true, className: styles.image, priority: priorityImage, sizes: "(max-width: 1200px) 100vw, 1200px" }), _jsx("div", { className: styles.heroOverlay, "aria-hidden": "true" }), primaryCategory && (_jsx("div", { className: styles.badgeOverlay, children: categoryUrl ? (_jsx(Link, { href: categoryUrl, className: badgeClassName(), title: primaryCategory.name, children: truncateLabel(primaryCategory.name, 20) })) : (_jsx("span", { className: badgeClassName(), title: primaryCategory.name, children: truncateLabel(primaryCategory.name, 20) })) })), _jsx("h1", { dangerouslySetInnerHTML: { __html: post.title.rendered }, className: `${styles.title} ${styles.heroTitle}` })] })), !featuredImage && (_jsx("header", { className: styles.postHeader, children: _jsx("h1", { dangerouslySetInnerHTML: { __html: post.title.rendered }, className: styles.title }) })), _jsxs("div", { className: styles.meta, children: [author && (_jsx("span", { className: styles.authorLabel, children: t("post.authorPrefix", { name: author.name }) })), _jsx("span", { children: "\u2022" }), _jsx("time", { dateTime: post.date, children: publishedDate })] }), _jsx("div", { dangerouslySetInnerHTML: { __html: contentHtml }, className: `post-content ${styles.content}` }), disclaimer && (_jsx(Alert, { className: styles.disclaimer, role: "note", children: disclaimer })), _jsx(LanguageLinks, { links: languageLinks }), author && (_jsx(Card, { as: "aside", className: styles.authorCard, size: "sm", children: _jsx(CardContent, { children: _jsxs("address", { className: styles.authorAddress, children: [_jsx("strong", { className: styles.authorName, children: author.name }), authorBio && _jsx("p", { className: styles.authorBio, children: authorBio })] }) }) }))] })] }));
33
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wpheadless-lib",
3
- "version": "1.1.11",
3
+ "version": "1.1.14",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",