ng-easycommerce-v18 0.3.4 → 0.3.6
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/README.md +4 -0
- package/esm2022/lib/constants/core.constants.service.mjs +77 -2
- package/esm2022/lib/ec-components/product-detail-ec/product-detail-ec.component.mjs +9 -85
- package/fesm2022/ng-easycommerce-v18.mjs +82 -83
- package/fesm2022/ng-easycommerce-v18.mjs.map +1 -1
- package/lib/constants/core.constants.service.d.ts +20 -0
- package/lib/ec-components/product-detail-ec/product-detail-ec.component.d.ts +1 -4
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, PLATFORM_ID, RendererFactory2, afterNextRender, signal, EnvironmentInjector, runInInjectionContext, Component, ChangeDetectorRef, HostListener, CUSTOM_ELEMENTS_SCHEMA, Input, Pipe, Injector, EventEmitter, Output, forwardRef, afterRender, ViewChild, computed, Renderer2, ChangeDetectionStrategy, Directive, Inject } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
|
-
import {
|
|
4
|
+
import { DOCUMENT, isPlatformBrowser, AsyncPipe, CommonModule, TitleCasePipe, JsonPipe, UpperCasePipe, Location } from '@angular/common';
|
|
5
5
|
import { take, BehaviorSubject, shareReplay, map, catchError, of, filter, ReplaySubject, firstValueFrom, concatMap, throwError, switchMap, combineLatest } from 'rxjs';
|
|
6
6
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
7
7
|
import * as i1$1 from '@ngx-translate/core';
|
|
@@ -225,6 +225,10 @@ class CoreConstantsService {
|
|
|
225
225
|
* Provee el ID de la plataforma en donde se esta ejecutando el codigo, esto sirve para poder diferenciar si es de un servidor o un navegador web.
|
|
226
226
|
*/
|
|
227
227
|
platformId = inject(PLATFORM_ID);
|
|
228
|
+
/**
|
|
229
|
+
* Document token para acceso SSR-compatible al documento
|
|
230
|
+
*/
|
|
231
|
+
document = inject(DOCUMENT);
|
|
228
232
|
/**
|
|
229
233
|
* Guarda la variable window del web browser.
|
|
230
234
|
*/
|
|
@@ -264,6 +268,77 @@ class CoreConstantsService {
|
|
|
264
268
|
* @returns
|
|
265
269
|
*/
|
|
266
270
|
url = () => this.apiConstants.API_URL.replace(/\/$/, ''); // remove last slash
|
|
271
|
+
/**
|
|
272
|
+
* URL del sitio frontend - Compatible con Angular SSR
|
|
273
|
+
*/
|
|
274
|
+
get FRONTEND_URL() {
|
|
275
|
+
// Verificar si estamos en el navegador
|
|
276
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
277
|
+
// Primero intenta leer de window.__env (configurado por Docker)
|
|
278
|
+
const windowEnv = this.window?.__env;
|
|
279
|
+
if (windowEnv?.frontendUrl) {
|
|
280
|
+
return windowEnv.frontendUrl;
|
|
281
|
+
}
|
|
282
|
+
// Si no hay configuración específica, construir desde window.location
|
|
283
|
+
if (this.window?.location) {
|
|
284
|
+
return `${this.window.location.protocol}//${this.window.location.host}`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Para SSR, intentar construir desde el document si está disponible
|
|
288
|
+
if (this.document?.location) {
|
|
289
|
+
return `${this.document.location.protocol}//${this.document.location.host}`;
|
|
290
|
+
}
|
|
291
|
+
// Fallback - retornar URL vacía
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Retorna la URL completa del frontend con una ruta opcional
|
|
296
|
+
* @param path - Ruta opcional para agregar a la URL base
|
|
297
|
+
* @returns {string} URL completa del frontend
|
|
298
|
+
*/
|
|
299
|
+
getFrontendUrl = (path) => {
|
|
300
|
+
const baseUrl = this.FRONTEND_URL;
|
|
301
|
+
if (!path) {
|
|
302
|
+
return baseUrl;
|
|
303
|
+
}
|
|
304
|
+
// Asegurar que la ruta comience con '/' y no tenga doble slash
|
|
305
|
+
const cleanPath = path.startsWith('/') ? path : `/${path}`;
|
|
306
|
+
const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
307
|
+
return `${cleanBaseUrl}${cleanPath}`;
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Retorna la URL absoluta de una imagen para meta tags (compatible con SSR)
|
|
311
|
+
* @param postMedia - Ruta de la imagen
|
|
312
|
+
* @returns {string} URL absoluta de la imagen
|
|
313
|
+
*/
|
|
314
|
+
getAbsoluteImageUrl = (postMedia) => {
|
|
315
|
+
// Si la URL ya es absoluta, devolverla tal como está
|
|
316
|
+
if (postMedia && (postMedia.startsWith('http://') || postMedia.startsWith('https://'))) {
|
|
317
|
+
return postMedia;
|
|
318
|
+
}
|
|
319
|
+
// Construir la URL de la imagen usando la URL del API
|
|
320
|
+
const imageUrl = this.mediaImageUrl(postMedia);
|
|
321
|
+
// Si la URL de la imagen ya es absoluta, devolverla
|
|
322
|
+
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
|
|
323
|
+
return imageUrl;
|
|
324
|
+
}
|
|
325
|
+
// Si es una URL relativa, convertirla a absoluta usando la URL base del API
|
|
326
|
+
const apiBaseUrl = this.apiConstants.API_URL;
|
|
327
|
+
if (apiBaseUrl.startsWith('http://') || apiBaseUrl.startsWith('https://')) {
|
|
328
|
+
// API_URL ya es absoluta
|
|
329
|
+
return imageUrl;
|
|
330
|
+
}
|
|
331
|
+
// Si API_URL es relativa, usar la URL del frontend como base
|
|
332
|
+
const frontendUrl = this.FRONTEND_URL;
|
|
333
|
+
if (!frontendUrl) {
|
|
334
|
+
return imageUrl; // Fallback si no podemos determinar la URL del frontend
|
|
335
|
+
}
|
|
336
|
+
// Construir URL absoluta
|
|
337
|
+
const cleanApiUrl = apiBaseUrl.startsWith('/') ? apiBaseUrl.substring(1) : apiBaseUrl;
|
|
338
|
+
const cleanFrontendUrl = frontendUrl.endsWith('/') ? frontendUrl.slice(0, -1) : frontendUrl;
|
|
339
|
+
const cleanImageUrl = imageUrl.startsWith('/') ? imageUrl.substring(1) : imageUrl;
|
|
340
|
+
return `${cleanFrontendUrl}/${cleanApiUrl}${cleanImageUrl}`;
|
|
341
|
+
};
|
|
267
342
|
/**
|
|
268
343
|
* Retorna la url de las imagenes de los banners
|
|
269
344
|
* @returns
|
|
@@ -8429,15 +8504,13 @@ class ProductDetailEcComponent {
|
|
|
8429
8504
|
_meta = inject(Meta);
|
|
8430
8505
|
_title = inject(Title);
|
|
8431
8506
|
_location = inject(Location);
|
|
8432
|
-
_router = inject(Router);
|
|
8433
|
-
_document = inject(DOCUMENT);
|
|
8434
|
-
_platformId = inject(PLATFORM_ID);
|
|
8435
8507
|
product$ = this._productDetailService.product$;
|
|
8436
8508
|
options$ = this._productDetailService.options$;
|
|
8437
8509
|
data$ = this._productDetailService.associatedData$;
|
|
8438
8510
|
routeSubscription;
|
|
8439
8511
|
route = inject(ActivatedRoute);
|
|
8440
8512
|
currentProductId;
|
|
8513
|
+
_router = inject(Router);
|
|
8441
8514
|
ngOnDestroy() {
|
|
8442
8515
|
this.routeSubscription?.unsubscribe();
|
|
8443
8516
|
}
|
|
@@ -8457,7 +8530,6 @@ class ProductDetailEcComponent {
|
|
|
8457
8530
|
creditAccountShowPrices = null;
|
|
8458
8531
|
// Agregar esta propiedad al inicio de la clase
|
|
8459
8532
|
productSignal = signal(null);
|
|
8460
|
-
lastMetaTagsProductId = null;
|
|
8461
8533
|
constructor() {
|
|
8462
8534
|
this._activedRoute.params.subscribe(queryParams => {
|
|
8463
8535
|
this.code = queryParams["id"];
|
|
@@ -8500,86 +8572,13 @@ class ProductDetailEcComponent {
|
|
|
8500
8572
|
}
|
|
8501
8573
|
updateMetaTags(product) {
|
|
8502
8574
|
const descripcionLimpia = he.decode(product.description || '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
}
|
|
8509
|
-
else {
|
|
8510
|
-
// En SSR, construir la URL completa manualmente
|
|
8511
|
-
const routerUrl = this._router.url;
|
|
8512
|
-
// Obtener el origin del documento o usar uno por defecto
|
|
8513
|
-
let origin = '';
|
|
8514
|
-
try {
|
|
8515
|
-
// Intentar obtener el origin del documento
|
|
8516
|
-
if (this._document.defaultView && this._document.defaultView.location) {
|
|
8517
|
-
origin = this._document.defaultView.location.origin;
|
|
8518
|
-
}
|
|
8519
|
-
else {
|
|
8520
|
-
// Fallback: construir el origin desde los headers de la request
|
|
8521
|
-
// Detectar el protocolo correcto del documento actual
|
|
8522
|
-
const protocol = this._document.location?.protocol ||
|
|
8523
|
-
(typeof window !== 'undefined' ? window.location.protocol : 'http:');
|
|
8524
|
-
const host = this._document.location?.host || 'localhost';
|
|
8525
|
-
origin = `${protocol}//${host}`;
|
|
8526
|
-
}
|
|
8527
|
-
}
|
|
8528
|
-
catch (error) {
|
|
8529
|
-
// Si falla, usar la URL base de las constantes como fallback
|
|
8530
|
-
const fallbackUrl = this._consts.url ? this._consts.url() :
|
|
8531
|
-
(typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}` : 'http://localhost');
|
|
8532
|
-
origin = fallbackUrl.replace(/\/$/, ''); // Remover trailing slash
|
|
8533
|
-
}
|
|
8534
|
-
currentUrl = origin + routerUrl;
|
|
8535
|
-
}
|
|
8536
|
-
// Verificar que el producto tenga datos válidos
|
|
8537
|
-
if (!product || !product.name) {
|
|
8538
|
-
console.warn('Product is null, undefined, or missing name for meta tags');
|
|
8539
|
-
return;
|
|
8540
|
-
}
|
|
8541
|
-
// Evitar actualizaciones duplicadas para el mismo producto
|
|
8542
|
-
const productId = String(product.id || product.name);
|
|
8543
|
-
if (this.lastMetaTagsProductId === productId) {
|
|
8544
|
-
return;
|
|
8545
|
-
}
|
|
8546
|
-
this.lastMetaTagsProductId = productId;
|
|
8547
|
-
// Log para debug (comentado para producción)
|
|
8548
|
-
// console.log('Updating meta tags for product:', {
|
|
8549
|
-
// name: product.name,
|
|
8550
|
-
// description: product.description,
|
|
8551
|
-
// pictures: product.picturesdefault
|
|
8552
|
-
// });
|
|
8553
|
-
// Preparar los valores con fallbacks más robustos
|
|
8554
|
-
const title = product.name || 'Producto sin nombre';
|
|
8555
|
-
const description = descripcionLimpia || 'Producto disponible';
|
|
8556
|
-
// Manejar la imagen de manera más robusta
|
|
8557
|
-
let imageUrl = '';
|
|
8558
|
-
if (product.picturesdefault) {
|
|
8559
|
-
const firstImage = Array.isArray(product.picturesdefault) ? product.picturesdefault[0] : product.picturesdefault;
|
|
8560
|
-
if (firstImage) {
|
|
8561
|
-
imageUrl = this._consts.mediaImageUrl(firstImage) || '';
|
|
8562
|
-
}
|
|
8563
|
-
}
|
|
8564
|
-
// Si no hay imagen, usar una imagen por defecto o vacía
|
|
8565
|
-
if (!imageUrl) {
|
|
8566
|
-
imageUrl = ''; // O puedes poner una imagen por defecto
|
|
8567
|
-
}
|
|
8568
|
-
// Actualizar las meta tags
|
|
8569
|
-
this._meta.updateTag({ property: 'og:title', content: title });
|
|
8570
|
-
this._meta.updateTag({ property: 'og:description', content: description });
|
|
8571
|
-
this._meta.updateTag({ property: 'og:image', content: imageUrl });
|
|
8575
|
+
const currentUrl = this._consts.getFrontendUrl() + this._router.url; // URL absoluta del producto
|
|
8576
|
+
const imageUrl = this._consts.getAbsoluteImageUrl(Array.isArray(product.picturesdefault) ? product.picturesdefault[0] : product.picturesdefault);
|
|
8577
|
+
this._meta.updateTag({ property: 'og:title', content: product.name || '' });
|
|
8578
|
+
this._meta.updateTag({ property: 'og:description', content: descripcionLimpia || '' });
|
|
8579
|
+
this._meta.updateTag({ property: 'og:image', content: imageUrl || '' });
|
|
8572
8580
|
this._meta.updateTag({ property: 'og:url', content: currentUrl });
|
|
8573
8581
|
this._meta.updateTag({ property: 'og:type', content: 'product' });
|
|
8574
|
-
// También actualizar el título de la página
|
|
8575
|
-
this._title.setTitle(title);
|
|
8576
|
-
// Log para verificar los valores finales (comentado para producción)
|
|
8577
|
-
// console.log('Meta tags updated:', {
|
|
8578
|
-
// title,
|
|
8579
|
-
// description,
|
|
8580
|
-
// imageUrl,
|
|
8581
|
-
// currentUrl
|
|
8582
|
-
// });
|
|
8583
8582
|
}
|
|
8584
8583
|
decodeHtml(html) {
|
|
8585
8584
|
const txt = document.createElement('textarea');
|