@jjlmoya/utils-pets 1.24.0 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -5
- package/scripts/postinstall.mjs +2 -4
- package/src/components/ProductionBreadcrumb.astro +134 -0
- package/src/components/ProductionStructuredData.astro +46 -0
- package/src/components/ProductionWidget.astro +182 -0
- package/src/i18n/header-ui.ts +15 -0
- package/src/i18n/language-ui.ts +9 -0
- package/src/i18n/languages.ts +24 -0
- package/src/identity/brands.ts +5 -0
- package/src/layouts/ProductionCategoryPage.astro +184 -0
- package/src/layouts/ProductionPage.astro +218 -0
- package/src/layouts/ProductionUtilityPage.astro +283 -0
- package/src/mfe/assets.ts +39 -0
- package/src/mfe/category-ui.ts +34 -0
- package/src/mfe/manifest.ts +45 -0
- package/src/mfe/routes.ts +50 -0
- package/src/mfe/widget-height.ts +14 -0
- package/src/pages/[locale]/[utilities]/[categories]/[category]/[slug]/manifest.json.ts +49 -0
- package/src/pages/[locale]/[utilities]/[categories]/[category]/[slug].astro +100 -0
- package/src/pages/[locale]/[utilities]/[categories]/[category].astro +44 -0
- package/src/pages/index.astro +1 -1
- package/src/pages/utilidades/[slug]/manifest.json.ts +28 -0
- package/src/pages/utilidades/[slug].astro +90 -0
- package/src/pages/utilidades/categorias/[category].astro +38 -0
- package/src/tests/mfe_cache_contract.test.ts +11 -0
- package/src/tests/mfe_manifest.test.ts +28 -0
- package/src/tests/registry_contract.test.ts +67 -0
- package/src/tests/seo_length.test.ts +1 -0
- package/src/types.ts +4 -6
- package/src/worker.ts +25 -0
- package/src/pages/[locale]/[slug].astro +0 -172
- package/src/pages/[locale].astro +0 -270
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import PreviewLayout from "../../layouts/PreviewLayout.astro";
|
|
3
|
-
import PreviewNavSidebar from "../../components/PreviewNavSidebar.astro";
|
|
4
|
-
import { ALL_TOOLS } from "../../index";
|
|
5
|
-
import {
|
|
6
|
-
UtilityHeader,
|
|
7
|
-
FAQSection,
|
|
8
|
-
Bibliography,
|
|
9
|
-
SEORenderer,
|
|
10
|
-
} from "@jjlmoya/utils-shared";
|
|
11
|
-
import type { KnownLocale, ToolLocaleContent } from "../../types";
|
|
12
|
-
import type { UtilitySEOContent } from "@jjlmoya/utils-shared";
|
|
13
|
-
|
|
14
|
-
export async function getStaticPaths() {
|
|
15
|
-
const paths = [];
|
|
16
|
-
|
|
17
|
-
for (const { entry, Component: lazyComp } of ALL_TOOLS) {
|
|
18
|
-
const componentLoader = lazyComp as () => Promise<{ default: ToolComponent }>;
|
|
19
|
-
const { default: Component } = await componentLoader();
|
|
20
|
-
const localeEntries = Object.entries(entry.i18n) as [
|
|
21
|
-
KnownLocale,
|
|
22
|
-
() => Promise<ToolLocaleContent>,
|
|
23
|
-
][];
|
|
24
|
-
const localeContents = await Promise.all(
|
|
25
|
-
localeEntries.map(async ([locale, loader]) => ({
|
|
26
|
-
locale,
|
|
27
|
-
content: await loader(),
|
|
28
|
-
})),
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const localeUrls = Object.fromEntries(
|
|
32
|
-
localeContents.map(({ locale, content }) => [
|
|
33
|
-
locale,
|
|
34
|
-
`/${locale}/${content.slug}`,
|
|
35
|
-
]),
|
|
36
|
-
) as Partial<Record<KnownLocale, string>>;
|
|
37
|
-
|
|
38
|
-
const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
|
|
39
|
-
const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
|
|
40
|
-
|
|
41
|
-
for (const { locale, content } of localeContents) {
|
|
42
|
-
const allToolsNav = (
|
|
43
|
-
await Promise.all(
|
|
44
|
-
ALL_TOOLS.map(async ({ entry: navEntry }) => {
|
|
45
|
-
const loader = navEntry.i18n[locale] ?? navEntry.i18n.en;
|
|
46
|
-
if (!loader) return null;
|
|
47
|
-
const navContent = await loader();
|
|
48
|
-
return {
|
|
49
|
-
id: navEntry.id,
|
|
50
|
-
title: navContent.title,
|
|
51
|
-
href: `/${locale}/${navContent.slug}`,
|
|
52
|
-
isActive: navEntry.id === entry.id,
|
|
53
|
-
};
|
|
54
|
-
}),
|
|
55
|
-
)
|
|
56
|
-
).filter(Boolean) as NavItem[];
|
|
57
|
-
paths.push({
|
|
58
|
-
params: { locale, slug: content.slug },
|
|
59
|
-
props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return paths;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface NavItem {
|
|
68
|
-
id: string;
|
|
69
|
-
title: string;
|
|
70
|
-
href: string;
|
|
71
|
-
isActive?: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface Props {
|
|
75
|
-
Component: ToolComponent;
|
|
76
|
-
locale: KnownLocale;
|
|
77
|
-
content: ToolLocaleContent;
|
|
78
|
-
localeUrls: Partial<Record<KnownLocale, string>>;
|
|
79
|
-
allToolsNav: NavItem[];
|
|
80
|
-
englishSlug: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
type ToolComponent = (props: { ui: Record<string, string> }) => unknown;
|
|
84
|
-
|
|
85
|
-
const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props as Props;
|
|
86
|
-
|
|
87
|
-
const cssFiles = import.meta.glob("../../tool/*/*.css", { query: "?raw", import: "default" });
|
|
88
|
-
const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
|
|
89
|
-
const cssLoader = cssKey ? cssFiles[cssKey] : undefined;
|
|
90
|
-
const toolCss = cssLoader ? await cssLoader() as string : "";
|
|
91
|
-
|
|
92
|
-
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
93
|
-
|
|
94
|
-
const words = content.title.split(" ");
|
|
95
|
-
const titleHighlight = words[0] || "";
|
|
96
|
-
const titleBase = words.slice(1).join(" ") || "";
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
<PreviewLayout
|
|
100
|
-
title={content.title}
|
|
101
|
-
currentLocale={locale}
|
|
102
|
-
localeUrls={localeUrls}
|
|
103
|
-
hasSidebar={true}
|
|
104
|
-
>
|
|
105
|
-
<PreviewNavSidebar
|
|
106
|
-
slot="sidebar"
|
|
107
|
-
categoryTitle="Tools"
|
|
108
|
-
tools={allToolsNav}
|
|
109
|
-
/>
|
|
110
|
-
<Fragment slot="head">
|
|
111
|
-
{toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
|
|
112
|
-
{
|
|
113
|
-
( content.schemas ?? []).map((schema) => (
|
|
114
|
-
<script
|
|
115
|
-
is:inline
|
|
116
|
-
type="application/ld+json"
|
|
117
|
-
set:html={JSON.stringify(schema)}
|
|
118
|
-
/>
|
|
119
|
-
))
|
|
120
|
-
}
|
|
121
|
-
</Fragment>
|
|
122
|
-
|
|
123
|
-
<div class="tool-page">
|
|
124
|
-
<UtilityHeader
|
|
125
|
-
titleHighlight={titleHighlight}
|
|
126
|
-
titleBase={titleBase}
|
|
127
|
-
description={content.description}
|
|
128
|
-
/>
|
|
129
|
-
|
|
130
|
-
<section class="section-tool">
|
|
131
|
-
<Component ui={content.ui} />
|
|
132
|
-
</section>
|
|
133
|
-
|
|
134
|
-
<section class="section-seo">
|
|
135
|
-
<SEORenderer content={seoContent} />
|
|
136
|
-
</section>
|
|
137
|
-
|
|
138
|
-
<section class="section-faq">
|
|
139
|
-
<FAQSection items={content.faq} />
|
|
140
|
-
</section>
|
|
141
|
-
|
|
142
|
-
<section class="section-bibliography">
|
|
143
|
-
<Bibliography links={content.bibliography} />
|
|
144
|
-
</section>
|
|
145
|
-
</div>
|
|
146
|
-
</PreviewLayout>
|
|
147
|
-
|
|
148
|
-
<style>
|
|
149
|
-
.tool-page {
|
|
150
|
-
display: flex;
|
|
151
|
-
flex-direction: column;
|
|
152
|
-
gap: 2rem;
|
|
153
|
-
min-width: 0;
|
|
154
|
-
width: 100%;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.section-tool {
|
|
158
|
-
max-width: 1200px;
|
|
159
|
-
margin: 0 auto;
|
|
160
|
-
min-width: 0;
|
|
161
|
-
width: 100%;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.section-seo,
|
|
165
|
-
.section-faq,
|
|
166
|
-
.section-bibliography {
|
|
167
|
-
min-width: 0;
|
|
168
|
-
width: 100%;
|
|
169
|
-
padding-top: 2rem;
|
|
170
|
-
border-top: 1px solid var(--border-color);
|
|
171
|
-
}
|
|
172
|
-
</style>
|
package/src/pages/[locale].astro
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import PreviewLayout from '../layouts/PreviewLayout.astro';
|
|
3
|
-
import PreviewNavSidebar from '../components/PreviewNavSidebar.astro';
|
|
4
|
-
import { petsCategory, ALL_TOOLS } from '../index';
|
|
5
|
-
import { Icon } from 'astro-icon/components';
|
|
6
|
-
import type { KnownLocale, ToolLocaleContent } from '../types';
|
|
7
|
-
|
|
8
|
-
export async function getStaticPaths() {
|
|
9
|
-
const locales = ['en', 'es', 'fr'] as KnownLocale[];
|
|
10
|
-
return locales.map(locale => ({ params: { locale } }));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const { locale: currentLocale } = Astro.params as { locale: KnownLocale };
|
|
14
|
-
|
|
15
|
-
const categoryContent = await petsCategory.i18n[currentLocale]!();
|
|
16
|
-
|
|
17
|
-
const tools = ALL_TOOLS || [];
|
|
18
|
-
|
|
19
|
-
const toolsWithContent = tools.length > 0
|
|
20
|
-
? await Promise.all(
|
|
21
|
-
tools.map(async ({ entry, Component }) => {
|
|
22
|
-
const languages = Object.keys(entry.i18n);
|
|
23
|
-
const localeEntries = await Promise.all(
|
|
24
|
-
languages.map(async (l) => {
|
|
25
|
-
const content = await entry.i18n[l as KnownLocale]!();
|
|
26
|
-
return [l, content];
|
|
27
|
-
})
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const localeContents = Object.fromEntries(localeEntries) as Record<string, ToolLocaleContent<Record<string, string>>>;
|
|
31
|
-
|
|
32
|
-
const currentLocaleContent = localeContents[currentLocale] || localeContents['en'] || localeContents['es'];
|
|
33
|
-
const availableLocales: Record<string, string> = {};
|
|
34
|
-
|
|
35
|
-
for (const l of languages) {
|
|
36
|
-
const lCont = localeContents[l];
|
|
37
|
-
if (lCont) {
|
|
38
|
-
availableLocales[l] = `/${l}/${lCont.slug}`;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return { entry, Component, locale: currentLocaleContent, availableLocales };
|
|
43
|
-
})
|
|
44
|
-
)
|
|
45
|
-
: [];
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
<PreviewLayout
|
|
49
|
-
title={categoryContent.title}
|
|
50
|
-
currentLocale={currentLocale}
|
|
51
|
-
hasSidebar={true}
|
|
52
|
-
>
|
|
53
|
-
<PreviewNavSidebar
|
|
54
|
-
slot="sidebar"
|
|
55
|
-
categoryTitle={categoryContent.title}
|
|
56
|
-
tools={toolsWithContent.map(({ entry, locale, availableLocales }) => {
|
|
57
|
-
const href = availableLocales[currentLocale] || (locale ? `/${currentLocale}/${locale.slug}` : '#');
|
|
58
|
-
return {
|
|
59
|
-
id: entry.id,
|
|
60
|
-
title: locale?.title || entry.id,
|
|
61
|
-
href: href,
|
|
62
|
-
};
|
|
63
|
-
})}
|
|
64
|
-
/>
|
|
65
|
-
<div class="dashboard">
|
|
66
|
-
<header class="preview-header">
|
|
67
|
-
<span class="badge">preview · @jjlmoya/utils-pets</span>
|
|
68
|
-
<h1>{categoryContent.title}</h1>
|
|
69
|
-
<p>{categoryContent.description}</p>
|
|
70
|
-
</header>
|
|
71
|
-
|
|
72
|
-
<div class="tool-list">
|
|
73
|
-
{toolsWithContent?.map(({ entry, locale, availableLocales }) => (
|
|
74
|
-
<article class="tool-card">
|
|
75
|
-
<a href={availableLocales?.[currentLocale] || (locale ? `/${currentLocale}/${locale.slug}` : '#')} class="tool-card-link">
|
|
76
|
-
<div class="tool-icons">
|
|
77
|
-
<div class="icon-wrapper bg">
|
|
78
|
-
<Icon name={entry.icons.bg} />
|
|
79
|
-
</div>
|
|
80
|
-
<div class="icon-wrapper fg">
|
|
81
|
-
<Icon name={entry.icons.fg} />
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
<div class="tool-card-content">
|
|
85
|
-
<h2 class="tool-title">{locale?.title}</h2>
|
|
86
|
-
<p class="tool-description">{locale?.description}</p>
|
|
87
|
-
</div>
|
|
88
|
-
<div class="tool-card-meta">
|
|
89
|
-
<span class="tool-id">{entry.id}</span>
|
|
90
|
-
</div>
|
|
91
|
-
</a>
|
|
92
|
-
|
|
93
|
-
{availableLocales && Object.keys(availableLocales).length > 1 && (
|
|
94
|
-
<div class="tool-locales">
|
|
95
|
-
{Object.entries(availableLocales).map(([l, url]) => (
|
|
96
|
-
<a href={url} class="locale-badge" title={`Ver en ${l.toUpperCase()}`} class:list={{ active: l === currentLocale }}>
|
|
97
|
-
{l.toUpperCase()}
|
|
98
|
-
</a>
|
|
99
|
-
))}
|
|
100
|
-
</div>
|
|
101
|
-
)}
|
|
102
|
-
</article>
|
|
103
|
-
))}
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
</PreviewLayout>
|
|
107
|
-
|
|
108
|
-
<style>
|
|
109
|
-
.dashboard {
|
|
110
|
-
display: flex;
|
|
111
|
-
flex-direction: column;
|
|
112
|
-
gap: 5rem;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.preview-header {
|
|
116
|
-
text-align: center;
|
|
117
|
-
padding-bottom: 3rem;
|
|
118
|
-
border-bottom: 1px solid var(--border-color);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.badge {
|
|
122
|
-
display: inline-block;
|
|
123
|
-
padding: 0.25rem 0.75rem;
|
|
124
|
-
background: var(--accent);
|
|
125
|
-
border-radius: 99px;
|
|
126
|
-
font-size: 0.7rem;
|
|
127
|
-
font-weight: 800;
|
|
128
|
-
margin-bottom: 1.5rem;
|
|
129
|
-
color: var(--text-base);
|
|
130
|
-
letter-spacing: 0.05em;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
h1 {
|
|
134
|
-
font-size: clamp(2rem, 6vw, 3.5rem);
|
|
135
|
-
font-weight: 900;
|
|
136
|
-
margin: 0 0 1rem;
|
|
137
|
-
background: linear-gradient(to bottom, var(--text-base), var(--text-muted));
|
|
138
|
-
-webkit-background-clip: text;
|
|
139
|
-
-webkit-text-fill-color: transparent;
|
|
140
|
-
background-clip: text;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.preview-header p {
|
|
144
|
-
color: var(--text-muted);
|
|
145
|
-
font-size: 1.1rem;
|
|
146
|
-
margin: 0;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.tool-list {
|
|
150
|
-
display: grid;
|
|
151
|
-
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
152
|
-
gap: 2rem;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.tool-card {
|
|
156
|
-
display: flex;
|
|
157
|
-
flex-direction: column;
|
|
158
|
-
gap: 1rem;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.tool-card-link {
|
|
162
|
-
flex: 1;
|
|
163
|
-
display: flex;
|
|
164
|
-
flex-direction: column;
|
|
165
|
-
padding: 1.5rem;
|
|
166
|
-
background: var(--bg-surface);
|
|
167
|
-
border: 1px solid var(--border-color);
|
|
168
|
-
border-radius: 0.75rem;
|
|
169
|
-
text-decoration: none;
|
|
170
|
-
transition: all 0.2s ease;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.tool-card-link:hover {
|
|
174
|
-
border-color: var(--accent);
|
|
175
|
-
background: rgba(244, 63, 94, 0.05);
|
|
176
|
-
transform: translateY(-2px);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.tool-icons {
|
|
180
|
-
display: flex;
|
|
181
|
-
align-items: center;
|
|
182
|
-
gap: 1rem;
|
|
183
|
-
margin-bottom: 1.25rem;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.icon-wrapper {
|
|
187
|
-
display: flex;
|
|
188
|
-
align-items: center;
|
|
189
|
-
justify-content: center;
|
|
190
|
-
width: 3rem;
|
|
191
|
-
height: 3rem;
|
|
192
|
-
border-radius: 0.5rem;
|
|
193
|
-
font-size: 1.5rem;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.icon-wrapper.bg {
|
|
197
|
-
background: var(--accent);
|
|
198
|
-
color: var(--text-base);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.icon-wrapper.fg {
|
|
202
|
-
background: var(--bg-page);
|
|
203
|
-
border: 1px solid var(--border-color);
|
|
204
|
-
color: var(--accent);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.tool-card-content {
|
|
208
|
-
flex: 1;
|
|
209
|
-
display: flex;
|
|
210
|
-
flex-direction: column;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.tool-title {
|
|
214
|
-
font-size: 1.25rem;
|
|
215
|
-
font-weight: 700;
|
|
216
|
-
margin: 0 0 0.5rem;
|
|
217
|
-
color: var(--text-base);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.tool-description {
|
|
221
|
-
font-size: 0.9375rem;
|
|
222
|
-
color: var(--text-muted);
|
|
223
|
-
line-height: 1.5;
|
|
224
|
-
margin: 0;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.tool-card-meta {
|
|
228
|
-
padding-top: 1rem;
|
|
229
|
-
border-top: 1px solid var(--border-color);
|
|
230
|
-
margin-top: 1.5rem;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.tool-id {
|
|
234
|
-
display: inline-block;
|
|
235
|
-
font-size: 0.7rem;
|
|
236
|
-
background: var(--bg-page);
|
|
237
|
-
border: 1px solid var(--border-color);
|
|
238
|
-
padding: 0.35rem 0.75rem;
|
|
239
|
-
border-radius: 0.4rem;
|
|
240
|
-
color: var(--accent);
|
|
241
|
-
font-weight: 600;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.tool-locales {
|
|
245
|
-
display: flex;
|
|
246
|
-
gap: 0.5rem;
|
|
247
|
-
flex-wrap: wrap;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
.locale-badge {
|
|
251
|
-
display: inline-block;
|
|
252
|
-
padding: 0.4rem 0.85rem;
|
|
253
|
-
background: var(--bg-page);
|
|
254
|
-
border: 1px solid var(--border-color);
|
|
255
|
-
border-radius: 0.4rem;
|
|
256
|
-
color: var(--text-muted);
|
|
257
|
-
text-decoration: none;
|
|
258
|
-
font-size: 0.75rem;
|
|
259
|
-
font-weight: 600;
|
|
260
|
-
text-transform: uppercase;
|
|
261
|
-
letter-spacing: 0.05em;
|
|
262
|
-
transition: all 0.15s ease;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.locale-badge:hover, .locale-badge.active {
|
|
266
|
-
color: var(--accent);
|
|
267
|
-
border-color: var(--accent);
|
|
268
|
-
background: rgba(244, 63, 94, 0.1);
|
|
269
|
-
}
|
|
270
|
-
</style>
|