optimising and making it better

This commit is contained in:
sdarbinyan
2026-02-26 21:54:21 +04:00
parent 7a00a8f1e3
commit 10b4974719
58 changed files with 318 additions and 1804 deletions

View File

@@ -0,0 +1,117 @@
import { Injectable, inject } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';
import { environment } from '../../environments/environment';
import { Item } from '../models';
import { getDiscountedPrice, getMainImage } from '../utils/item.utils';
@Injectable({
providedIn: 'root'
})
export class SeoService {
private meta = inject(Meta);
private title = inject(Title);
private readonly siteUrl = `https://${environment.domain}`;
private readonly siteName = environment.brandFullName;
/**
* Set Open Graph & Twitter Card meta tags for a product/item page.
*/
setItemMeta(item: Item): void {
const price = item.discount > 0 ? getDiscountedPrice(item) : item.price;
const imageUrl = this.resolveUrl(getMainImage(item));
const itemUrl = `${this.siteUrl}/item/${item.itemID}`;
const description = this.truncate(this.stripHtml(item.description), 160);
const titleText = `${item.name}${this.siteName}`;
this.title.setTitle(titleText);
this.setOrUpdate([
// Open Graph
{ property: 'og:type', content: 'product' },
{ property: 'og:title', content: item.name },
{ property: 'og:description', content: description },
{ property: 'og:image', content: imageUrl },
{ property: 'og:url', content: itemUrl },
{ property: 'og:site_name', content: this.siteName },
{ property: 'og:locale', content: 'ru_RU' },
// Product-specific OG tags
{ property: 'product:price:amount', content: price.toFixed(2) },
{ property: 'product:price:currency', content: item.currency || 'RUB' },
// Twitter Card
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: item.name },
{ name: 'twitter:description', content: description },
{ name: 'twitter:image', content: imageUrl },
// Standard meta
{ name: 'description', content: description },
]);
}
/**
* Reset meta tags back to defaults (call on navigation away from item page).
*/
resetToDefaults(): void {
const defaultTitle = `${this.siteName} — Маркетплейс товаров и услуг`;
const defaultDescription = 'Современный маркетплейс для покупки цифровых товаров. Широкий выбор товаров, удобный поиск, быстрая доставка.';
const defaultImage = `${this.siteUrl}/og-image.jpg`;
this.title.setTitle(defaultTitle);
this.setOrUpdate([
{ property: 'og:type', content: 'website' },
{ property: 'og:title', content: defaultTitle },
{ property: 'og:description', content: defaultDescription },
{ property: 'og:image', content: defaultImage },
{ property: 'og:url', content: this.siteUrl },
{ property: 'og:site_name', content: this.siteName },
{ property: 'og:locale', content: 'ru_RU' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: defaultTitle },
{ name: 'twitter:description', content: defaultDescription },
{ name: 'twitter:image', content: defaultImage },
{ name: 'description', content: defaultDescription },
]);
// Remove product-specific tags
this.meta.removeTag("property='product:price:amount'");
this.meta.removeTag("property='product:price:currency'");
}
private setOrUpdate(tags: Array<{ property?: string; name?: string; content: string }>): void {
for (const tag of tags) {
const selector = tag.property
? `property='${tag.property}'`
: `name='${tag.name}'`;
const existing = this.meta.getTag(selector);
if (existing) {
this.meta.updateTag(tag as any, selector);
} else {
this.meta.addTag(tag as any);
}
}
}
/** Convert relative URLs to absolute */
private resolveUrl(url: string): string {
if (!url || url.startsWith('http')) return url;
return `${this.siteUrl}${url.startsWith('/') ? '' : '/'}${url}`;
}
/** Strip HTML tags from a string */
private stripHtml(html: string): string {
return html?.replace(/<[^>]*>/g, '') || '';
}
/** Truncate text to maxLength, adding ellipsis */
private truncate(text: string, maxLength: number): string {
if (!text || text.length <= maxLength) return text || '';
return text.substring(0, maxLength - 1) + '…';
}
}