@snabcentr/client-core 4.8.0 → 4.10.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.
|
@@ -2,7 +2,7 @@ import * as i1 from 'rxjs';
|
|
|
2
2
|
import { of, startWith as startWith$1, Subject, ReplaySubject, partition, combineLatest, map as map$1, debounceTime, switchMap, tap, filter, shareReplay, distinctUntilChanged, BehaviorSubject, materialize, dematerialize, merge, distinctUntilKeyChanged, skip, first, catchError as catchError$1, throwError, expand, takeWhile, toArray, EMPTY, take, defaultIfEmpty, concatMap, finalize, takeUntil, timeout, interval, share, scan } from 'rxjs';
|
|
3
3
|
import { map, startWith, catchError } from 'rxjs/operators';
|
|
4
4
|
import { parseISO, parse, isValid, format } from 'date-fns';
|
|
5
|
-
import { isString, isNil, isObject, isDate, isMatch, isArray, get, isNumber, isBoolean } from 'lodash-es';
|
|
5
|
+
import { isString, isNil, isObject, isDate, isMatch, isArray, omit, get, isNumber, isBoolean } from 'lodash-es';
|
|
6
6
|
import * as i0 from '@angular/core';
|
|
7
7
|
import { InjectionToken, inject, PLATFORM_ID, Inject, Optional, Injectable, EventEmitter, DestroyRef, LOCALE_ID, Pipe, RESPONSE_INIT, RendererFactory2 } from '@angular/core';
|
|
8
8
|
import { WA_LOCAL_STORAGE, WA_USER_AGENT, WA_WINDOW } from '@ng-web-apis/common';
|
|
@@ -7635,36 +7635,36 @@ class SchemaOrgFactory {
|
|
|
7635
7635
|
*
|
|
7636
7636
|
* @param product Данные о продукте.
|
|
7637
7637
|
* @param productPath Url путь к товару. В пути должен присутствовать шаблон ':id', обозначающий идентификатор товара для подстановки.
|
|
7638
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7638
7639
|
*/
|
|
7639
|
-
generateForProductItem(product, productPath) {
|
|
7640
|
+
generateForProductItem(product, productPath, id) {
|
|
7640
7641
|
const productUrl = `${this.urls.siteUrl}${productPath.replace(':id', this.idOrSlugPipe.transform(product).toString())}`;
|
|
7641
7642
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
7642
|
-
const image = this.mediaImageTransformerPipe.transform(product?.getImagePreview() ?? product.image, true);
|
|
7643
|
+
const image = this.mediaImageTransformerPipe.transform(product?.getImagePreview?.() ?? product.image, true);
|
|
7643
7644
|
const description = product.properties?.description;
|
|
7644
7645
|
const priceValidUntil = this.normalizePriceValidUntil(product.discount?.expiredAt);
|
|
7645
7646
|
return {
|
|
7646
7647
|
'@context': 'https://schema.org',
|
|
7647
7648
|
'@type': 'Product',
|
|
7649
|
+
...(id && { '@id': id }),
|
|
7648
7650
|
name: product.name,
|
|
7649
7651
|
...(image && { image }),
|
|
7650
7652
|
...(description && { description }),
|
|
7651
7653
|
mpn: product.code,
|
|
7652
7654
|
url: productUrl,
|
|
7653
|
-
offers:
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
name: this.companyInfo.name,
|
|
7664
|
-
},
|
|
7665
|
-
url: productUrl,
|
|
7655
|
+
offers: {
|
|
7656
|
+
'@type': 'Offer',
|
|
7657
|
+
priceCurrency: 'RUB',
|
|
7658
|
+
...(priceValidUntil && { priceValidUntil }),
|
|
7659
|
+
price: product.costRub,
|
|
7660
|
+
availability: this.getAvailability(product),
|
|
7661
|
+
itemCondition: 'https://schema.org/NewCondition',
|
|
7662
|
+
seller: {
|
|
7663
|
+
'@type': 'Organization',
|
|
7664
|
+
name: this.companyInfo.name,
|
|
7666
7665
|
},
|
|
7667
|
-
|
|
7666
|
+
url: productUrl,
|
|
7667
|
+
},
|
|
7668
7668
|
};
|
|
7669
7669
|
}
|
|
7670
7670
|
/**
|
|
@@ -7675,13 +7675,15 @@ class SchemaOrgFactory {
|
|
|
7675
7675
|
* @param categoryPath Url путь к категориям товаров. В пути должен присутствовать шаблон ':id', обозначающий идентификатор категории для подстановки.
|
|
7676
7676
|
* @param productPath Url путь к товару. В пути должен присутствовать шаблон ':id', обозначающий идентификатор товара для подстановки.
|
|
7677
7677
|
* @param breadcrumbs Данные о хлебных крошках для категории.
|
|
7678
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7678
7679
|
*/
|
|
7679
|
-
generateForCategoryItem(category, products, categoryPath, productPath, breadcrumbs) {
|
|
7680
|
+
generateForCategoryItem(category, products, categoryPath, productPath, breadcrumbs, id) {
|
|
7680
7681
|
const description = category.seo?.description ?? category.properties?.description;
|
|
7681
7682
|
const image = this.mediaImageTransformerPipe.transform(category.image, true);
|
|
7682
7683
|
return {
|
|
7683
7684
|
'@context': 'https://schema.org',
|
|
7684
7685
|
'@type': 'CollectionPage',
|
|
7686
|
+
...(id && { '@id': id }),
|
|
7685
7687
|
name: category.seo?.title ?? category.name,
|
|
7686
7688
|
...(description && { description }),
|
|
7687
7689
|
url: this.getCategoryCanonicalUrl(category, categoryPath),
|
|
@@ -7699,12 +7701,6 @@ class SchemaOrgFactory {
|
|
|
7699
7701
|
name: category.name,
|
|
7700
7702
|
...(description && { description }),
|
|
7701
7703
|
},
|
|
7702
|
-
// Информация о сайте.
|
|
7703
|
-
isPartOf: {
|
|
7704
|
-
'@type': 'WebSite',
|
|
7705
|
-
name: this.companyInfo.name,
|
|
7706
|
-
url: this.urls.siteUrl,
|
|
7707
|
-
},
|
|
7708
7704
|
// Связанные страницы.
|
|
7709
7705
|
relatedLink: [
|
|
7710
7706
|
`${this.urls.siteUrl}${categoryPath.replace(':id', '')}`,
|
|
@@ -7745,6 +7741,223 @@ class SchemaOrgFactory {
|
|
|
7745
7741
|
],
|
|
7746
7742
|
};
|
|
7747
7743
|
}
|
|
7744
|
+
/**
|
|
7745
|
+
* Генерирует и возвращает схему данных OnlineStore на основе данных компании из окружения.
|
|
7746
|
+
*/
|
|
7747
|
+
// eslint-disable-next-line sonarjs/function-return-type
|
|
7748
|
+
generateForOrganization() {
|
|
7749
|
+
const postalAddress = this.createPostalAddress(this.companyInfo.address);
|
|
7750
|
+
return {
|
|
7751
|
+
'@context': 'https://schema.org',
|
|
7752
|
+
'@type': 'OnlineStore',
|
|
7753
|
+
'@id': `${this.urls.siteUrl}#organization`,
|
|
7754
|
+
name: this.companyInfo.name,
|
|
7755
|
+
legalName: this.companyInfo.name,
|
|
7756
|
+
url: this.urls.siteUrl,
|
|
7757
|
+
logo: this.urls.logoUrl,
|
|
7758
|
+
image: this.urls.logoUrl,
|
|
7759
|
+
email: this.companyInfo.mail,
|
|
7760
|
+
telephone: this.companyInfo.phone,
|
|
7761
|
+
...(postalAddress && { address: postalAddress }),
|
|
7762
|
+
legalAddress: this.createPostalAddress({ streetAddress: this.companyInfo.legalAddress }),
|
|
7763
|
+
// TODO: Перечислить все розничные магазины.
|
|
7764
|
+
// hasPOS: [],
|
|
7765
|
+
taxID: this.companyInfo.inn,
|
|
7766
|
+
identifier: {
|
|
7767
|
+
'@type': 'PropertyValue',
|
|
7768
|
+
propertyID: 'OGRN',
|
|
7769
|
+
value: this.companyInfo.ogrn,
|
|
7770
|
+
},
|
|
7771
|
+
contactPoint: this.createOrganizationContactPoints(),
|
|
7772
|
+
...(this.companyInfo.warehouseAddress.length > 0 && {
|
|
7773
|
+
location: this.companyInfo.warehouseAddress.map((warehouseAddress) => ({
|
|
7774
|
+
'@type': 'Place',
|
|
7775
|
+
name: `${this.companyInfo.name} — склад самовывоза`,
|
|
7776
|
+
address: warehouseAddress,
|
|
7777
|
+
})),
|
|
7778
|
+
}),
|
|
7779
|
+
};
|
|
7780
|
+
}
|
|
7781
|
+
/**
|
|
7782
|
+
* Генерирует и возвращает схему данных для указанной новости.
|
|
7783
|
+
*
|
|
7784
|
+
* @param news Данные о новости.
|
|
7785
|
+
* @param newsPath Url путь к новости. В пути должен присутствовать шаблон ':id', обозначающий идентификатор новости для подстановки.
|
|
7786
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7787
|
+
*/
|
|
7788
|
+
generateForNewsArticle(news, newsPath, id) {
|
|
7789
|
+
const newsUrl = `${this.urls.siteUrl}${newsPath.replace(':id', news.id.toString())}`;
|
|
7790
|
+
const image = this.mediaImageTransformerPipe.transform(news.image, true);
|
|
7791
|
+
const headline = news.seo?.title ?? news.subject;
|
|
7792
|
+
const description = news.seo?.description;
|
|
7793
|
+
const datePublished = this.normalizeSchemaDate(news.createdAt);
|
|
7794
|
+
const articleBody = news.text ? this.stripHtml(news.text) : undefined;
|
|
7795
|
+
return {
|
|
7796
|
+
'@context': 'https://schema.org',
|
|
7797
|
+
'@type': 'NewsArticle',
|
|
7798
|
+
...(id && { '@id': id }),
|
|
7799
|
+
headline,
|
|
7800
|
+
...(description && { description }),
|
|
7801
|
+
...(image && { image }),
|
|
7802
|
+
...(datePublished && { datePublished }),
|
|
7803
|
+
...(articleBody && { articleBody }),
|
|
7804
|
+
url: newsUrl,
|
|
7805
|
+
mainEntityOfPage: newsUrl,
|
|
7806
|
+
author: {
|
|
7807
|
+
'@type': 'Organization',
|
|
7808
|
+
name: this.companyInfo.name,
|
|
7809
|
+
url: this.urls.siteUrl,
|
|
7810
|
+
},
|
|
7811
|
+
publisher: {
|
|
7812
|
+
'@type': 'Organization',
|
|
7813
|
+
name: this.companyInfo.name,
|
|
7814
|
+
logo: {
|
|
7815
|
+
'@type': 'ImageObject',
|
|
7816
|
+
url: this.urls.logoUrl,
|
|
7817
|
+
},
|
|
7818
|
+
},
|
|
7819
|
+
};
|
|
7820
|
+
}
|
|
7821
|
+
/**
|
|
7822
|
+
* Генерирует и возвращает схему данных FAQPage для страницы с вопросами и ответами.
|
|
7823
|
+
*
|
|
7824
|
+
* @param items Список вопросов и ответов.
|
|
7825
|
+
* @param pagePath Url путь к странице FAQ.
|
|
7826
|
+
* @param pageName Необязательное название страницы.
|
|
7827
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7828
|
+
*/
|
|
7829
|
+
generateForFaqPage(items, pagePath, pageName, id) {
|
|
7830
|
+
const pageUrl = `${this.urls.siteUrl}${pagePath}`;
|
|
7831
|
+
return {
|
|
7832
|
+
'@context': 'https://schema.org',
|
|
7833
|
+
'@type': 'FAQPage',
|
|
7834
|
+
...(id && { '@id': id }),
|
|
7835
|
+
...(pageName && { name: pageName }),
|
|
7836
|
+
url: pageUrl,
|
|
7837
|
+
mainEntity: items.map((item) => ({
|
|
7838
|
+
'@type': 'Question',
|
|
7839
|
+
name: item.question,
|
|
7840
|
+
acceptedAnswer: {
|
|
7841
|
+
'@type': 'Answer',
|
|
7842
|
+
text: this.stripHtml(item.answer),
|
|
7843
|
+
},
|
|
7844
|
+
})),
|
|
7845
|
+
};
|
|
7846
|
+
}
|
|
7847
|
+
/**
|
|
7848
|
+
* Объединяет несколько JSON-LD сущностей в один документ с `@graph`.
|
|
7849
|
+
*
|
|
7850
|
+
* @param data Массив JSON-LD сущностей для объединения.
|
|
7851
|
+
*/
|
|
7852
|
+
// eslint-disable-next-line class-methods-use-this,sonarjs/function-return-type
|
|
7853
|
+
merge(data) {
|
|
7854
|
+
return {
|
|
7855
|
+
'@context': 'https://schema.org',
|
|
7856
|
+
'@graph': data.map((item) => omit(item, '@context')),
|
|
7857
|
+
};
|
|
7858
|
+
}
|
|
7859
|
+
/**
|
|
7860
|
+
* Формирует PostalAddress из структурированного адреса компании.
|
|
7861
|
+
*
|
|
7862
|
+
* @param address Структурированный адрес компании.
|
|
7863
|
+
*/
|
|
7864
|
+
// eslint-disable-next-line class-methods-use-this
|
|
7865
|
+
createPostalAddress(address) {
|
|
7866
|
+
const streetAddress = [address.streetAddress, address.extendedAddress].filter(Boolean).join(', ');
|
|
7867
|
+
if (!streetAddress && !address.locality && !address.region && !address.postalCode && !address.country) {
|
|
7868
|
+
return undefined;
|
|
7869
|
+
}
|
|
7870
|
+
return {
|
|
7871
|
+
'@type': 'PostalAddress',
|
|
7872
|
+
...(streetAddress && { streetAddress }),
|
|
7873
|
+
...(address.locality && { addressLocality: address.locality }),
|
|
7874
|
+
...(address.region && { addressRegion: address.region }),
|
|
7875
|
+
...(address.postalCode && { postalCode: address.postalCode }),
|
|
7876
|
+
...(address.country && { addressCountry: address.country }),
|
|
7877
|
+
};
|
|
7878
|
+
}
|
|
7879
|
+
/**
|
|
7880
|
+
* Формирует список ContactPoint для Organization.
|
|
7881
|
+
*/
|
|
7882
|
+
createOrganizationContactPoints() {
|
|
7883
|
+
const contactPoints = [
|
|
7884
|
+
{
|
|
7885
|
+
'@type': 'ContactPoint',
|
|
7886
|
+
contactType: 'customer service',
|
|
7887
|
+
telephone: this.companyInfo.phone,
|
|
7888
|
+
email: this.companyInfo.mail,
|
|
7889
|
+
areaServed: 'RU',
|
|
7890
|
+
availableLanguage: 'Russian',
|
|
7891
|
+
},
|
|
7892
|
+
];
|
|
7893
|
+
if (this.companyInfo.secondaryPhone) {
|
|
7894
|
+
contactPoints.push({
|
|
7895
|
+
'@type': 'ContactPoint',
|
|
7896
|
+
contactType: 'customer service',
|
|
7897
|
+
telephone: this.companyInfo.secondaryPhone,
|
|
7898
|
+
areaServed: 'RU',
|
|
7899
|
+
availableLanguage: 'Russian',
|
|
7900
|
+
});
|
|
7901
|
+
}
|
|
7902
|
+
if (this.companyInfo.hr) {
|
|
7903
|
+
contactPoints.push({
|
|
7904
|
+
'@type': 'ContactPoint',
|
|
7905
|
+
contactType: 'HR',
|
|
7906
|
+
telephone: this.companyInfo.hr.phone,
|
|
7907
|
+
email: this.companyInfo.hr.mail,
|
|
7908
|
+
areaServed: 'RU',
|
|
7909
|
+
availableLanguage: 'Russian',
|
|
7910
|
+
});
|
|
7911
|
+
}
|
|
7912
|
+
return contactPoints;
|
|
7913
|
+
}
|
|
7914
|
+
/**
|
|
7915
|
+
* Удаляет HTML-разметку из текста.
|
|
7916
|
+
*
|
|
7917
|
+
* @param html Текст с HTML-разметкой.
|
|
7918
|
+
*/
|
|
7919
|
+
// eslint-disable-next-line class-methods-use-this
|
|
7920
|
+
stripHtml(html) {
|
|
7921
|
+
const withoutTags = html
|
|
7922
|
+
.split('<')
|
|
7923
|
+
.map((part, index) => {
|
|
7924
|
+
if (index === 0) {
|
|
7925
|
+
return part;
|
|
7926
|
+
}
|
|
7927
|
+
const closeTagIndex = part.indexOf('>');
|
|
7928
|
+
return closeTagIndex === -1 ? part : ` ${part.slice(closeTagIndex + 1)}`;
|
|
7929
|
+
})
|
|
7930
|
+
.join('');
|
|
7931
|
+
return withoutTags
|
|
7932
|
+
.replaceAll(/\s+/g, ' ')
|
|
7933
|
+
.replaceAll(/ ([.,!?;:])/g, '$1')
|
|
7934
|
+
.trim();
|
|
7935
|
+
}
|
|
7936
|
+
/**
|
|
7937
|
+
* Нормализует дату в формат Schema.org (ISO 8601).
|
|
7938
|
+
*
|
|
7939
|
+
* @param dateValue Дата для нормализации.
|
|
7940
|
+
*/
|
|
7941
|
+
// eslint-disable-next-line class-methods-use-this
|
|
7942
|
+
normalizeSchemaDate(dateValue) {
|
|
7943
|
+
if (!dateValue) {
|
|
7944
|
+
return undefined;
|
|
7945
|
+
}
|
|
7946
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(dateValue)) {
|
|
7947
|
+
return dateValue;
|
|
7948
|
+
}
|
|
7949
|
+
const withTimeMatch = /^(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2})$/.exec(dateValue);
|
|
7950
|
+
if (withTimeMatch) {
|
|
7951
|
+
const [, day, month, year, hours, minutes] = withTimeMatch;
|
|
7952
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:00`;
|
|
7953
|
+
}
|
|
7954
|
+
const dateOnlyMatch = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec(dateValue);
|
|
7955
|
+
if (!dateOnlyMatch) {
|
|
7956
|
+
return undefined;
|
|
7957
|
+
}
|
|
7958
|
+
const [, day, month, year] = dateOnlyMatch;
|
|
7959
|
+
return `${year}-${month}-${day}`;
|
|
7960
|
+
}
|
|
7748
7961
|
/**
|
|
7749
7962
|
* Нормализует дату окончания цены в формат Schema.org (YYYY-MM-DD).
|
|
7750
7963
|
*
|
|
@@ -7766,9 +7979,9 @@ class SchemaOrgFactory {
|
|
|
7766
7979
|
// eslint-disable-next-line class-methods-use-this
|
|
7767
7980
|
getAvailability(product) {
|
|
7768
7981
|
if (product.onOrder) {
|
|
7769
|
-
return 'PreOrder';
|
|
7982
|
+
return 'https://schema.org/PreOrder';
|
|
7770
7983
|
}
|
|
7771
|
-
return product.stockCount?.length ? 'InStock' : 'OutOfStock';
|
|
7984
|
+
return product.stockCount?.length ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock';
|
|
7772
7985
|
}
|
|
7773
7986
|
/**
|
|
7774
7987
|
* Проверяет, является ли категория виртуальной.
|
|
@@ -7812,21 +8025,19 @@ class SchemaOrgFactory {
|
|
|
7812
8025
|
url: productUrl,
|
|
7813
8026
|
...(image && { image }),
|
|
7814
8027
|
...(description && { description }),
|
|
7815
|
-
offers:
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
name: this.companyInfo.name,
|
|
7826
|
-
},
|
|
7827
|
-
url: productUrl,
|
|
8028
|
+
offers: {
|
|
8029
|
+
'@type': 'Offer',
|
|
8030
|
+
priceCurrency: 'RUB',
|
|
8031
|
+
...(priceValidUntil && { priceValidUntil }),
|
|
8032
|
+
price: product.costRub,
|
|
8033
|
+
availability: this.getAvailability(product),
|
|
8034
|
+
itemCondition: 'https://schema.org/NewCondition',
|
|
8035
|
+
seller: {
|
|
8036
|
+
'@type': 'Organization',
|
|
8037
|
+
name: this.companyInfo.name,
|
|
7828
8038
|
},
|
|
7829
|
-
|
|
8039
|
+
url: productUrl,
|
|
8040
|
+
},
|
|
7830
8041
|
},
|
|
7831
8042
|
};
|
|
7832
8043
|
}
|
|
@@ -7848,14 +8059,21 @@ class SchemaOrgFactory {
|
|
|
7848
8059
|
itemListOrder: 'https://schema.org/ItemListUnordered',
|
|
7849
8060
|
itemListElement: [
|
|
7850
8061
|
// Добавляем подкатегории в начало списка.
|
|
7851
|
-
...childCategories.slice(0, 5).map((subCategory, index) =>
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
8062
|
+
...childCategories.slice(0, 5).map((subCategory, index) => {
|
|
8063
|
+
const image = this.mediaImageTransformerPipe.transform(subCategory.image, true);
|
|
8064
|
+
const description = subCategory.seo?.description ?? subCategory.properties?.description;
|
|
8065
|
+
return {
|
|
8066
|
+
'@type': 'ListItem',
|
|
8067
|
+
position: index + 1,
|
|
8068
|
+
item: {
|
|
8069
|
+
'@type': 'CollectionPage',
|
|
8070
|
+
name: subCategory.name,
|
|
8071
|
+
url: this.getCategoryCanonicalUrl(subCategory, categoryPath),
|
|
8072
|
+
...(image && { image }),
|
|
8073
|
+
...(description && { description }),
|
|
8074
|
+
},
|
|
8075
|
+
};
|
|
8076
|
+
}),
|
|
7859
8077
|
// Добавляем товары после подкатегорий.
|
|
7860
8078
|
...products.slice(0, 10).map((product, index) => this.createProductsListItem(product, childCategories.length + index + 1, productPath)),
|
|
7861
8079
|
],
|
|
@@ -7868,37 +8086,46 @@ class SchemaOrgFactory {
|
|
|
7868
8086
|
* @param categoryPath Url путь к категориям товаров. В пути должен присутствовать шаблон ':id', обозначающий идентификатор категории для подстановки.
|
|
7869
8087
|
*/
|
|
7870
8088
|
createCatalogBreadcrumbsList(breadcrumbs, categoryPath) {
|
|
8089
|
+
const catalogUrl = `${this.urls.siteUrl}${categoryPath.replace(':id', '')}`;
|
|
7871
8090
|
const breadcrumbItems = [
|
|
7872
8091
|
// Всегда добавляем главную страницу.
|
|
7873
|
-
|
|
7874
|
-
'@type': 'ListItem',
|
|
7875
|
-
position: 1,
|
|
7876
|
-
name: 'Главная',
|
|
7877
|
-
item: this.urls.siteUrl,
|
|
7878
|
-
},
|
|
8092
|
+
this.createBreadcrumbListItem(1, 'Главная', this.urls.siteUrl),
|
|
7879
8093
|
// Добавляем каталог.
|
|
7880
|
-
|
|
7881
|
-
'@type': 'ListItem',
|
|
7882
|
-
position: 2,
|
|
7883
|
-
name: 'Каталог',
|
|
7884
|
-
item: `${this.urls.siteUrl}/${categoryPath}`,
|
|
7885
|
-
},
|
|
8094
|
+
this.createBreadcrumbListItem(2, 'Каталог', catalogUrl),
|
|
7886
8095
|
];
|
|
7887
8096
|
// Добавляем существующие breadcrumbs с корректировкой позиций.
|
|
7888
8097
|
breadcrumbs.forEach((item, index) => {
|
|
7889
8098
|
const link = isArray(item.routerLink) ? item.routerLink.join('/') : item.routerLink;
|
|
7890
|
-
breadcrumbItems.push({
|
|
7891
|
-
'@type': 'ListItem',
|
|
7892
|
-
position: index + 3, // Начинаем с позиции 3 (после Главная и Каталог).
|
|
7893
|
-
name: item.label,
|
|
7894
|
-
item: link ? `${this.urls.siteUrl}/${link}` : undefined,
|
|
7895
|
-
});
|
|
8099
|
+
breadcrumbItems.push(this.createBreadcrumbListItem(index + 3, item.label, link ? `${this.urls.siteUrl}/${link}` : undefined));
|
|
7896
8100
|
});
|
|
7897
8101
|
return {
|
|
7898
8102
|
'@type': 'BreadcrumbList',
|
|
8103
|
+
itemListOrder: 'https://schema.org/ItemListOrderAscending',
|
|
7899
8104
|
itemListElement: breadcrumbItems,
|
|
7900
8105
|
};
|
|
7901
8106
|
}
|
|
8107
|
+
/**
|
|
8108
|
+
* Создаёт элемент BreadcrumbList в формате, рекомендованном Schema.org.
|
|
8109
|
+
*
|
|
8110
|
+
* @param position Позиция элемента в цепочке.
|
|
8111
|
+
* @param name Название элемента.
|
|
8112
|
+
* @param url URL элемента.
|
|
8113
|
+
*/
|
|
8114
|
+
// eslint-disable-next-line class-methods-use-this
|
|
8115
|
+
createBreadcrumbListItem(position, name, url) {
|
|
8116
|
+
return {
|
|
8117
|
+
'@type': 'ListItem',
|
|
8118
|
+
position,
|
|
8119
|
+
...(url
|
|
8120
|
+
? {
|
|
8121
|
+
item: {
|
|
8122
|
+
'@id': url,
|
|
8123
|
+
name,
|
|
8124
|
+
},
|
|
8125
|
+
}
|
|
8126
|
+
: { name }),
|
|
8127
|
+
};
|
|
8128
|
+
}
|
|
7902
8129
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: SchemaOrgFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
7903
8130
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: SchemaOrgFactory, providedIn: 'root' }); }
|
|
7904
8131
|
}
|
|
@@ -7954,6 +8181,10 @@ class ScSeoService {
|
|
|
7954
8181
|
* Объект {@link Document}, предоставляющий доступ к DOM страницы.
|
|
7955
8182
|
*/
|
|
7956
8183
|
this.document = inject(DOCUMENT);
|
|
8184
|
+
/**
|
|
8185
|
+
* Фабрика для создания данных Schema.org.
|
|
8186
|
+
*/
|
|
8187
|
+
this.schemaOrgFactory = inject(SchemaOrgFactory);
|
|
7957
8188
|
/**
|
|
7958
8189
|
* Фабрика для создания экземпляров {@link Renderer2}.
|
|
7959
8190
|
*/
|
|
@@ -7969,8 +8200,9 @@ class ScSeoService {
|
|
|
7969
8200
|
* @param resource Ресурс на основе которого необходимо установить мета-теги.
|
|
7970
8201
|
* @param img Изображение. Если не пришло, то будет установлено значение по умолчанию.
|
|
7971
8202
|
* @param robots Список robots-тегов для страницы.
|
|
8203
|
+
* @param schemaOrgData Данные Schema.org для встраивания на странице.
|
|
7972
8204
|
*/
|
|
7973
|
-
setFromResource(resource, img
|
|
8205
|
+
setFromResource(resource, img, robots, schemaOrgData) {
|
|
7974
8206
|
const updatedResource = {
|
|
7975
8207
|
title: this.replaceTemplates(resource?.title ?? this.defaultSeo.title, resource?.instanceData ?? {}),
|
|
7976
8208
|
description: this.replaceTemplates(resource?.description ?? this.defaultSeo.description, resource?.instanceData ?? {}),
|
|
@@ -7978,21 +8210,24 @@ class ScSeoService {
|
|
|
7978
8210
|
meta: resource?.meta ?? [],
|
|
7979
8211
|
};
|
|
7980
8212
|
const url = `${this.urls.siteUrl}${this.router.url}`;
|
|
7981
|
-
this.setMetaTags(updatedResource.title, updatedResource.description, updatedResource.keywords, updatedResource.meta, robots);
|
|
7982
|
-
this.setOpenGraphMetaTags(updatedResource.title, updatedResource.description, url, img);
|
|
8213
|
+
this.setMetaTags(updatedResource.title, updatedResource.description, updatedResource.keywords, updatedResource.meta, robots ?? ['index', 'follow']);
|
|
8214
|
+
this.setOpenGraphMetaTags(updatedResource.title, updatedResource.description, url, img ?? this.urls.logoUrl);
|
|
7983
8215
|
this.setCanonicalLink(url);
|
|
8216
|
+
// Устанавливаем Schema.org параметры.
|
|
8217
|
+
this.setSchemaOrgData(this.schemaOrgFactory.merge([this.schemaOrgFactory.generateForOrganization(), ...(schemaOrgData ? [schemaOrgData] : [])]));
|
|
7984
8218
|
}
|
|
7985
8219
|
/**
|
|
7986
8220
|
* Устанавливает seo параметры со значением по умолчанию.
|
|
7987
8221
|
*
|
|
7988
8222
|
* @param title Заголовок страницы.
|
|
7989
8223
|
* @param robots Список robots тегов для страницы.
|
|
8224
|
+
* @param schemaOrgData Данные Schema.org для встраивания на странице.
|
|
7990
8225
|
*/
|
|
7991
|
-
setByDefault(title, robots
|
|
8226
|
+
setByDefault(title, robots, schemaOrgData) {
|
|
7992
8227
|
this.setFromResource({
|
|
7993
8228
|
title: `${title ?? this.defaultSeo.title} - ${this.companyInfo.name}`,
|
|
7994
8229
|
description: title ? `${title}, ${this.defaultSeo.description}` : this.defaultSeo.description,
|
|
7995
|
-
}, this.urls.logoUrl, robots);
|
|
8230
|
+
}, this.urls.logoUrl, robots ?? ['index', 'follow'], schemaOrgData);
|
|
7996
8231
|
}
|
|
7997
8232
|
/**
|
|
7998
8233
|
* Устанавливает seo параметры со значением по умолчанию. Устанавливает значение "не индексировать" для поисковых роботов.
|
|
@@ -8023,9 +8258,7 @@ class ScSeoService {
|
|
|
8023
8258
|
this.setOpenGraphMetaTags(title, description, url, this.mediaImageTransformerPipe.transform(item.image, true));
|
|
8024
8259
|
this.setCanonicalLink(url);
|
|
8025
8260
|
// Устанавливаем Schema.org параметры.
|
|
8026
|
-
|
|
8027
|
-
this.setSchemaOrgData(schemaOrgData);
|
|
8028
|
-
}
|
|
8261
|
+
this.setSchemaOrgData(this.schemaOrgFactory.merge([this.schemaOrgFactory.generateForOrganization(), ...(schemaOrgData ? [schemaOrgData] : [])]));
|
|
8029
8262
|
}
|
|
8030
8263
|
/**
|
|
8031
8264
|
* Заменяет шаблоны в тексте на значения из объекта.
|