@snabcentr/client-core 2.68.0 → 2.70.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/esm2022/seo/classes/schema-org-factory.mjs +294 -67
- package/esm2022/seo/services/sc-seo.service.mjs +16 -9
- package/fesm2022/snabcentr-client-core.mjs +307 -74
- package/fesm2022/snabcentr-client-core.mjs.map +1 -1
- package/package.json +1 -1
- package/release_notes.tmp +6 -0
- package/seo/classes/schema-org-factory.d.ts +65 -4
- package/seo/services/sc-seo.service.d.ts +8 -2
|
@@ -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, 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, isMatch, isArray, get } from 'lodash-es';
|
|
5
|
+
import { isString, isNil, isMatch, isArray, omit, get } from 'lodash-es';
|
|
6
6
|
import * as i0 from '@angular/core';
|
|
7
7
|
import { InjectionToken, inject, PLATFORM_ID, Injectable, Inject, Optional, EventEmitter, DestroyRef, LOCALE_ID, Pipe, RendererFactory2 } from '@angular/core';
|
|
8
8
|
import { WA_LOCAL_STORAGE, WA_USER_AGENT, WA_WINDOW } from '@ng-web-apis/common';
|
|
@@ -7644,36 +7644,36 @@ class SchemaOrgFactory {
|
|
|
7644
7644
|
*
|
|
7645
7645
|
* @param product Данные о продукте.
|
|
7646
7646
|
* @param productPath Url путь к товару. В пути должен присутствовать шаблон ':id', обозначающий идентификатор товара для подстановки.
|
|
7647
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7647
7648
|
*/
|
|
7648
|
-
generateForProductItem(product, productPath) {
|
|
7649
|
+
generateForProductItem(product, productPath, id) {
|
|
7649
7650
|
const productUrl = `${this.urls.siteUrl}${productPath.replace(':id', this.idOrSlugPipe.transform(product).toString())}`;
|
|
7650
7651
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
7651
|
-
const image = this.mediaImageTransformerPipe.transform(product?.getImagePreview() ?? product.image, true);
|
|
7652
|
+
const image = this.mediaImageTransformerPipe.transform(product?.getImagePreview?.() ?? product.image, true);
|
|
7652
7653
|
const description = product.properties?.description;
|
|
7653
7654
|
const priceValidUntil = this.normalizePriceValidUntil(product.discount?.expiredAt);
|
|
7654
7655
|
return {
|
|
7655
7656
|
'@context': 'https://schema.org',
|
|
7656
7657
|
'@type': 'Product',
|
|
7658
|
+
...(id && { '@id': id }),
|
|
7657
7659
|
name: product.name,
|
|
7658
7660
|
...(image && { image }),
|
|
7659
7661
|
...(description && { description }),
|
|
7660
7662
|
mpn: product.code,
|
|
7661
7663
|
url: productUrl,
|
|
7662
|
-
offers:
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
name: this.companyInfo.name,
|
|
7673
|
-
},
|
|
7674
|
-
url: productUrl,
|
|
7664
|
+
offers: {
|
|
7665
|
+
'@type': 'Offer',
|
|
7666
|
+
priceCurrency: 'RUB',
|
|
7667
|
+
...(priceValidUntil && { priceValidUntil }),
|
|
7668
|
+
price: product.costRub,
|
|
7669
|
+
availability: this.getAvailability(product),
|
|
7670
|
+
itemCondition: 'https://schema.org/NewCondition',
|
|
7671
|
+
seller: {
|
|
7672
|
+
'@type': 'Organization',
|
|
7673
|
+
name: this.companyInfo.name,
|
|
7675
7674
|
},
|
|
7676
|
-
|
|
7675
|
+
url: productUrl,
|
|
7676
|
+
},
|
|
7677
7677
|
};
|
|
7678
7678
|
}
|
|
7679
7679
|
/**
|
|
@@ -7684,13 +7684,15 @@ class SchemaOrgFactory {
|
|
|
7684
7684
|
* @param categoryPath Url путь к категориям товаров. В пути должен присутствовать шаблон ':id', обозначающий идентификатор категории для подстановки.
|
|
7685
7685
|
* @param productPath Url путь к товару. В пути должен присутствовать шаблон ':id', обозначающий идентификатор товара для подстановки.
|
|
7686
7686
|
* @param breadcrumbs Данные о хлебных крошках для категории.
|
|
7687
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7687
7688
|
*/
|
|
7688
|
-
generateForCategoryItem(category, products, categoryPath, productPath, breadcrumbs) {
|
|
7689
|
+
generateForCategoryItem(category, products, categoryPath, productPath, breadcrumbs, id) {
|
|
7689
7690
|
const description = category.seo?.description ?? category.properties?.description;
|
|
7690
7691
|
const image = this.mediaImageTransformerPipe.transform(category.image, true);
|
|
7691
7692
|
return {
|
|
7692
7693
|
'@context': 'https://schema.org',
|
|
7693
7694
|
'@type': 'CollectionPage',
|
|
7695
|
+
...(id && { '@id': id }),
|
|
7694
7696
|
name: category.seo?.title ?? category.name,
|
|
7695
7697
|
...(description && { description }),
|
|
7696
7698
|
url: this.getCategoryCanonicalUrl(category, categoryPath),
|
|
@@ -7708,12 +7710,6 @@ class SchemaOrgFactory {
|
|
|
7708
7710
|
name: category.name,
|
|
7709
7711
|
...(description && { description }),
|
|
7710
7712
|
},
|
|
7711
|
-
// Информация о сайте.
|
|
7712
|
-
isPartOf: {
|
|
7713
|
-
'@type': 'WebSite',
|
|
7714
|
-
name: this.companyInfo.name,
|
|
7715
|
-
url: this.urls.siteUrl,
|
|
7716
|
-
},
|
|
7717
7713
|
// Связанные страницы.
|
|
7718
7714
|
relatedLink: [
|
|
7719
7715
|
`${this.urls.siteUrl}${categoryPath.replace(':id', '')}`,
|
|
@@ -7754,6 +7750,223 @@ class SchemaOrgFactory {
|
|
|
7754
7750
|
],
|
|
7755
7751
|
};
|
|
7756
7752
|
}
|
|
7753
|
+
/**
|
|
7754
|
+
* Генерирует и возвращает схему данных OnlineStore на основе данных компании из окружения.
|
|
7755
|
+
*/
|
|
7756
|
+
// eslint-disable-next-line sonarjs/function-return-type
|
|
7757
|
+
generateForOrganization() {
|
|
7758
|
+
const postalAddress = this.createPostalAddress(this.companyInfo.address);
|
|
7759
|
+
return {
|
|
7760
|
+
'@context': 'https://schema.org',
|
|
7761
|
+
'@type': 'OnlineStore',
|
|
7762
|
+
'@id': `${this.urls.siteUrl}#organization`,
|
|
7763
|
+
name: this.companyInfo.name,
|
|
7764
|
+
legalName: this.companyInfo.name,
|
|
7765
|
+
url: this.urls.siteUrl,
|
|
7766
|
+
logo: this.urls.logoUrl,
|
|
7767
|
+
image: this.urls.logoUrl,
|
|
7768
|
+
email: this.companyInfo.mail,
|
|
7769
|
+
telephone: this.companyInfo.phone,
|
|
7770
|
+
...(postalAddress && { address: postalAddress }),
|
|
7771
|
+
legalAddress: this.createPostalAddress({ streetAddress: this.companyInfo.legalAddress }),
|
|
7772
|
+
// TODO: Перечислить все розничные магазины.
|
|
7773
|
+
// hasPOS: [],
|
|
7774
|
+
taxID: this.companyInfo.inn,
|
|
7775
|
+
identifier: {
|
|
7776
|
+
'@type': 'PropertyValue',
|
|
7777
|
+
propertyID: 'OGRN',
|
|
7778
|
+
value: this.companyInfo.ogrn,
|
|
7779
|
+
},
|
|
7780
|
+
contactPoint: this.createOrganizationContactPoints(),
|
|
7781
|
+
...(this.companyInfo.warehouseAddress.length > 0 && {
|
|
7782
|
+
location: this.companyInfo.warehouseAddress.map((warehouseAddress) => ({
|
|
7783
|
+
'@type': 'Place',
|
|
7784
|
+
name: `${this.companyInfo.name} — склад самовывоза`,
|
|
7785
|
+
address: warehouseAddress,
|
|
7786
|
+
})),
|
|
7787
|
+
}),
|
|
7788
|
+
};
|
|
7789
|
+
}
|
|
7790
|
+
/**
|
|
7791
|
+
* Генерирует и возвращает схему данных для указанной новости.
|
|
7792
|
+
*
|
|
7793
|
+
* @param news Данные о новости.
|
|
7794
|
+
* @param newsPath Url путь к новости. В пути должен присутствовать шаблон ':id', обозначающий идентификатор новости для подстановки.
|
|
7795
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7796
|
+
*/
|
|
7797
|
+
generateForNewsArticle(news, newsPath, id) {
|
|
7798
|
+
const newsUrl = `${this.urls.siteUrl}${newsPath.replace(':id', news.id.toString())}`;
|
|
7799
|
+
const image = this.mediaImageTransformerPipe.transform(news.image, true);
|
|
7800
|
+
const headline = news.seo?.title ?? news.subject;
|
|
7801
|
+
const description = news.seo?.description;
|
|
7802
|
+
const datePublished = this.normalizeSchemaDate(news.createdAt);
|
|
7803
|
+
const articleBody = news.text ? this.stripHtml(news.text) : undefined;
|
|
7804
|
+
return {
|
|
7805
|
+
'@context': 'https://schema.org',
|
|
7806
|
+
'@type': 'NewsArticle',
|
|
7807
|
+
...(id && { '@id': id }),
|
|
7808
|
+
headline,
|
|
7809
|
+
...(description && { description }),
|
|
7810
|
+
...(image && { image }),
|
|
7811
|
+
...(datePublished && { datePublished }),
|
|
7812
|
+
...(articleBody && { articleBody }),
|
|
7813
|
+
url: newsUrl,
|
|
7814
|
+
mainEntityOfPage: newsUrl,
|
|
7815
|
+
author: {
|
|
7816
|
+
'@type': 'Organization',
|
|
7817
|
+
name: this.companyInfo.name,
|
|
7818
|
+
url: this.urls.siteUrl,
|
|
7819
|
+
},
|
|
7820
|
+
publisher: {
|
|
7821
|
+
'@type': 'Organization',
|
|
7822
|
+
name: this.companyInfo.name,
|
|
7823
|
+
logo: {
|
|
7824
|
+
'@type': 'ImageObject',
|
|
7825
|
+
url: this.urls.logoUrl,
|
|
7826
|
+
},
|
|
7827
|
+
},
|
|
7828
|
+
};
|
|
7829
|
+
}
|
|
7830
|
+
/**
|
|
7831
|
+
* Генерирует и возвращает схему данных FAQPage для страницы с вопросами и ответами.
|
|
7832
|
+
*
|
|
7833
|
+
* @param items Список вопросов и ответов.
|
|
7834
|
+
* @param pagePath Url путь к странице FAQ.
|
|
7835
|
+
* @param pageName Необязательное название страницы.
|
|
7836
|
+
* @param id Необязательный идентификатор сущности для поля `@id` в JSON-LD.
|
|
7837
|
+
*/
|
|
7838
|
+
generateForFaqPage(items, pagePath, pageName, id) {
|
|
7839
|
+
const pageUrl = `${this.urls.siteUrl}${pagePath}`;
|
|
7840
|
+
return {
|
|
7841
|
+
'@context': 'https://schema.org',
|
|
7842
|
+
'@type': 'FAQPage',
|
|
7843
|
+
...(id && { '@id': id }),
|
|
7844
|
+
...(pageName && { name: pageName }),
|
|
7845
|
+
url: pageUrl,
|
|
7846
|
+
mainEntity: items.map((item) => ({
|
|
7847
|
+
'@type': 'Question',
|
|
7848
|
+
name: item.question,
|
|
7849
|
+
acceptedAnswer: {
|
|
7850
|
+
'@type': 'Answer',
|
|
7851
|
+
text: this.stripHtml(item.answer),
|
|
7852
|
+
},
|
|
7853
|
+
})),
|
|
7854
|
+
};
|
|
7855
|
+
}
|
|
7856
|
+
/**
|
|
7857
|
+
* Объединяет несколько JSON-LD сущностей в один документ с `@graph`.
|
|
7858
|
+
*
|
|
7859
|
+
* @param data Массив JSON-LD сущностей для объединения.
|
|
7860
|
+
*/
|
|
7861
|
+
// eslint-disable-next-line class-methods-use-this,sonarjs/function-return-type
|
|
7862
|
+
merge(data) {
|
|
7863
|
+
return {
|
|
7864
|
+
'@context': 'https://schema.org',
|
|
7865
|
+
'@graph': data.map((item) => omit(item, '@context')),
|
|
7866
|
+
};
|
|
7867
|
+
}
|
|
7868
|
+
/**
|
|
7869
|
+
* Формирует PostalAddress из структурированного адреса компании.
|
|
7870
|
+
*
|
|
7871
|
+
* @param address Структурированный адрес компании.
|
|
7872
|
+
*/
|
|
7873
|
+
// eslint-disable-next-line class-methods-use-this
|
|
7874
|
+
createPostalAddress(address) {
|
|
7875
|
+
const streetAddress = [address.streetAddress, address.extendedAddress].filter(Boolean).join(', ');
|
|
7876
|
+
if (!streetAddress && !address.locality && !address.region && !address.postalCode && !address.country) {
|
|
7877
|
+
return undefined;
|
|
7878
|
+
}
|
|
7879
|
+
return {
|
|
7880
|
+
'@type': 'PostalAddress',
|
|
7881
|
+
...(streetAddress && { streetAddress }),
|
|
7882
|
+
...(address.locality && { addressLocality: address.locality }),
|
|
7883
|
+
...(address.region && { addressRegion: address.region }),
|
|
7884
|
+
...(address.postalCode && { postalCode: address.postalCode }),
|
|
7885
|
+
...(address.country && { addressCountry: address.country }),
|
|
7886
|
+
};
|
|
7887
|
+
}
|
|
7888
|
+
/**
|
|
7889
|
+
* Формирует список ContactPoint для Organization.
|
|
7890
|
+
*/
|
|
7891
|
+
createOrganizationContactPoints() {
|
|
7892
|
+
const contactPoints = [
|
|
7893
|
+
{
|
|
7894
|
+
'@type': 'ContactPoint',
|
|
7895
|
+
contactType: 'customer service',
|
|
7896
|
+
telephone: this.companyInfo.phone,
|
|
7897
|
+
email: this.companyInfo.mail,
|
|
7898
|
+
areaServed: 'RU',
|
|
7899
|
+
availableLanguage: 'Russian',
|
|
7900
|
+
},
|
|
7901
|
+
];
|
|
7902
|
+
if (this.companyInfo.secondaryPhone) {
|
|
7903
|
+
contactPoints.push({
|
|
7904
|
+
'@type': 'ContactPoint',
|
|
7905
|
+
contactType: 'customer service',
|
|
7906
|
+
telephone: this.companyInfo.secondaryPhone,
|
|
7907
|
+
areaServed: 'RU',
|
|
7908
|
+
availableLanguage: 'Russian',
|
|
7909
|
+
});
|
|
7910
|
+
}
|
|
7911
|
+
if (this.companyInfo.hr) {
|
|
7912
|
+
contactPoints.push({
|
|
7913
|
+
'@type': 'ContactPoint',
|
|
7914
|
+
contactType: 'HR',
|
|
7915
|
+
telephone: this.companyInfo.hr.phone,
|
|
7916
|
+
email: this.companyInfo.hr.mail,
|
|
7917
|
+
areaServed: 'RU',
|
|
7918
|
+
availableLanguage: 'Russian',
|
|
7919
|
+
});
|
|
7920
|
+
}
|
|
7921
|
+
return contactPoints;
|
|
7922
|
+
}
|
|
7923
|
+
/**
|
|
7924
|
+
* Удаляет HTML-разметку из текста.
|
|
7925
|
+
*
|
|
7926
|
+
* @param html Текст с HTML-разметкой.
|
|
7927
|
+
*/
|
|
7928
|
+
// eslint-disable-next-line class-methods-use-this
|
|
7929
|
+
stripHtml(html) {
|
|
7930
|
+
const withoutTags = html
|
|
7931
|
+
.split('<')
|
|
7932
|
+
.map((part, index) => {
|
|
7933
|
+
if (index === 0) {
|
|
7934
|
+
return part;
|
|
7935
|
+
}
|
|
7936
|
+
const closeTagIndex = part.indexOf('>');
|
|
7937
|
+
return closeTagIndex === -1 ? part : ` ${part.slice(closeTagIndex + 1)}`;
|
|
7938
|
+
})
|
|
7939
|
+
.join('');
|
|
7940
|
+
return withoutTags
|
|
7941
|
+
.replaceAll(/\s+/g, ' ')
|
|
7942
|
+
.replaceAll(/ ([.,!?;:])/g, '$1')
|
|
7943
|
+
.trim();
|
|
7944
|
+
}
|
|
7945
|
+
/**
|
|
7946
|
+
* Нормализует дату в формат Schema.org (ISO 8601).
|
|
7947
|
+
*
|
|
7948
|
+
* @param dateValue Дата для нормализации.
|
|
7949
|
+
*/
|
|
7950
|
+
// eslint-disable-next-line class-methods-use-this
|
|
7951
|
+
normalizeSchemaDate(dateValue) {
|
|
7952
|
+
if (!dateValue) {
|
|
7953
|
+
return undefined;
|
|
7954
|
+
}
|
|
7955
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(dateValue)) {
|
|
7956
|
+
return dateValue;
|
|
7957
|
+
}
|
|
7958
|
+
const withTimeMatch = /^(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2})$/.exec(dateValue);
|
|
7959
|
+
if (withTimeMatch) {
|
|
7960
|
+
const [, day, month, year, hours, minutes] = withTimeMatch;
|
|
7961
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:00`;
|
|
7962
|
+
}
|
|
7963
|
+
const dateOnlyMatch = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec(dateValue);
|
|
7964
|
+
if (!dateOnlyMatch) {
|
|
7965
|
+
return undefined;
|
|
7966
|
+
}
|
|
7967
|
+
const [, day, month, year] = dateOnlyMatch;
|
|
7968
|
+
return `${year}-${month}-${day}`;
|
|
7969
|
+
}
|
|
7757
7970
|
/**
|
|
7758
7971
|
* Нормализует дату окончания цены в формат Schema.org (YYYY-MM-DD).
|
|
7759
7972
|
*
|
|
@@ -7775,9 +7988,9 @@ class SchemaOrgFactory {
|
|
|
7775
7988
|
// eslint-disable-next-line class-methods-use-this
|
|
7776
7989
|
getAvailability(product) {
|
|
7777
7990
|
if (product.onOrder) {
|
|
7778
|
-
return 'PreOrder';
|
|
7991
|
+
return 'https://schema.org/PreOrder';
|
|
7779
7992
|
}
|
|
7780
|
-
return product.stockCount?.length ? 'InStock' : 'OutOfStock';
|
|
7993
|
+
return product.stockCount?.length ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock';
|
|
7781
7994
|
}
|
|
7782
7995
|
/**
|
|
7783
7996
|
* Проверяет, является ли категория виртуальной.
|
|
@@ -7821,21 +8034,19 @@ class SchemaOrgFactory {
|
|
|
7821
8034
|
url: productUrl,
|
|
7822
8035
|
...(image && { image }),
|
|
7823
8036
|
...(description && { description }),
|
|
7824
|
-
offers:
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
name: this.companyInfo.name,
|
|
7835
|
-
},
|
|
7836
|
-
url: productUrl,
|
|
8037
|
+
offers: {
|
|
8038
|
+
'@type': 'Offer',
|
|
8039
|
+
priceCurrency: 'RUB',
|
|
8040
|
+
...(priceValidUntil && { priceValidUntil }),
|
|
8041
|
+
price: product.costRub,
|
|
8042
|
+
availability: this.getAvailability(product),
|
|
8043
|
+
itemCondition: 'https://schema.org/NewCondition',
|
|
8044
|
+
seller: {
|
|
8045
|
+
'@type': 'Organization',
|
|
8046
|
+
name: this.companyInfo.name,
|
|
7837
8047
|
},
|
|
7838
|
-
|
|
8048
|
+
url: productUrl,
|
|
8049
|
+
},
|
|
7839
8050
|
},
|
|
7840
8051
|
};
|
|
7841
8052
|
}
|
|
@@ -7857,14 +8068,21 @@ class SchemaOrgFactory {
|
|
|
7857
8068
|
itemListOrder: 'https://schema.org/ItemListUnordered',
|
|
7858
8069
|
itemListElement: [
|
|
7859
8070
|
// Добавляем подкатегории в начало списка.
|
|
7860
|
-
...childCategories.slice(0, 5).map((subCategory, index) =>
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
8071
|
+
...childCategories.slice(0, 5).map((subCategory, index) => {
|
|
8072
|
+
const image = this.mediaImageTransformerPipe.transform(subCategory.image, true);
|
|
8073
|
+
const description = subCategory.seo?.description ?? subCategory.properties?.description;
|
|
8074
|
+
return {
|
|
8075
|
+
'@type': 'ListItem',
|
|
8076
|
+
position: index + 1,
|
|
8077
|
+
item: {
|
|
8078
|
+
'@type': 'CollectionPage',
|
|
8079
|
+
name: subCategory.name,
|
|
8080
|
+
url: this.getCategoryCanonicalUrl(subCategory, categoryPath),
|
|
8081
|
+
...(image && { image }),
|
|
8082
|
+
...(description && { description }),
|
|
8083
|
+
},
|
|
8084
|
+
};
|
|
8085
|
+
}),
|
|
7868
8086
|
// Добавляем товары после подкатегорий.
|
|
7869
8087
|
...products.slice(0, 10).map((product, index) => this.createProductsListItem(product, childCategories.length + index + 1, productPath)),
|
|
7870
8088
|
],
|
|
@@ -7877,37 +8095,46 @@ class SchemaOrgFactory {
|
|
|
7877
8095
|
* @param categoryPath Url путь к категориям товаров. В пути должен присутствовать шаблон ':id', обозначающий идентификатор категории для подстановки.
|
|
7878
8096
|
*/
|
|
7879
8097
|
createCatalogBreadcrumbsList(breadcrumbs, categoryPath) {
|
|
8098
|
+
const catalogUrl = `${this.urls.siteUrl}${categoryPath.replace(':id', '')}`;
|
|
7880
8099
|
const breadcrumbItems = [
|
|
7881
8100
|
// Всегда добавляем главную страницу.
|
|
7882
|
-
|
|
7883
|
-
'@type': 'ListItem',
|
|
7884
|
-
position: 1,
|
|
7885
|
-
name: 'Главная',
|
|
7886
|
-
item: this.urls.siteUrl,
|
|
7887
|
-
},
|
|
8101
|
+
this.createBreadcrumbListItem(1, 'Главная', this.urls.siteUrl),
|
|
7888
8102
|
// Добавляем каталог.
|
|
7889
|
-
|
|
7890
|
-
'@type': 'ListItem',
|
|
7891
|
-
position: 2,
|
|
7892
|
-
name: 'Каталог',
|
|
7893
|
-
item: `${this.urls.siteUrl}/${categoryPath}`,
|
|
7894
|
-
},
|
|
8103
|
+
this.createBreadcrumbListItem(2, 'Каталог', catalogUrl),
|
|
7895
8104
|
];
|
|
7896
8105
|
// Добавляем существующие breadcrumbs с корректировкой позиций.
|
|
7897
8106
|
breadcrumbs.forEach((item, index) => {
|
|
7898
8107
|
const link = isArray(item.routerLink) ? item.routerLink.join('/') : item.routerLink;
|
|
7899
|
-
breadcrumbItems.push({
|
|
7900
|
-
'@type': 'ListItem',
|
|
7901
|
-
position: index + 3, // Начинаем с позиции 3 (после Главная и Каталог).
|
|
7902
|
-
name: item.label,
|
|
7903
|
-
item: link ? `${this.urls.siteUrl}/${link}` : undefined,
|
|
7904
|
-
});
|
|
8108
|
+
breadcrumbItems.push(this.createBreadcrumbListItem(index + 3, item.label, link ? `${this.urls.siteUrl}/${link}` : undefined));
|
|
7905
8109
|
});
|
|
7906
8110
|
return {
|
|
7907
8111
|
'@type': 'BreadcrumbList',
|
|
8112
|
+
itemListOrder: 'https://schema.org/ItemListOrderAscending',
|
|
7908
8113
|
itemListElement: breadcrumbItems,
|
|
7909
8114
|
};
|
|
7910
8115
|
}
|
|
8116
|
+
/**
|
|
8117
|
+
* Создаёт элемент BreadcrumbList в формате, рекомендованном Schema.org.
|
|
8118
|
+
*
|
|
8119
|
+
* @param position Позиция элемента в цепочке.
|
|
8120
|
+
* @param name Название элемента.
|
|
8121
|
+
* @param url URL элемента.
|
|
8122
|
+
*/
|
|
8123
|
+
// eslint-disable-next-line class-methods-use-this
|
|
8124
|
+
createBreadcrumbListItem(position, name, url) {
|
|
8125
|
+
return {
|
|
8126
|
+
'@type': 'ListItem',
|
|
8127
|
+
position,
|
|
8128
|
+
...(url
|
|
8129
|
+
? {
|
|
8130
|
+
item: {
|
|
8131
|
+
'@id': url,
|
|
8132
|
+
name,
|
|
8133
|
+
},
|
|
8134
|
+
}
|
|
8135
|
+
: { name }),
|
|
8136
|
+
};
|
|
8137
|
+
}
|
|
7911
8138
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SchemaOrgFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
7912
8139
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SchemaOrgFactory, providedIn: 'root' }); }
|
|
7913
8140
|
}
|
|
@@ -7963,6 +8190,10 @@ class ScSeoService {
|
|
|
7963
8190
|
* Объект {@link Document}, предоставляющий доступ к DOM страницы.
|
|
7964
8191
|
*/
|
|
7965
8192
|
this.document = inject(DOCUMENT);
|
|
8193
|
+
/**
|
|
8194
|
+
* Фабрика для создания данных Schema.org.
|
|
8195
|
+
*/
|
|
8196
|
+
this.schemaOrgFactory = inject(SchemaOrgFactory);
|
|
7966
8197
|
/**
|
|
7967
8198
|
* Фабрика для создания экземпляров {@link Renderer2}.
|
|
7968
8199
|
*/
|
|
@@ -7978,8 +8209,9 @@ class ScSeoService {
|
|
|
7978
8209
|
* @param resource Ресурс на основе которого необходимо установить мета-теги.
|
|
7979
8210
|
* @param img Изображение. Если не пришло, то будет установлено значение по умолчанию.
|
|
7980
8211
|
* @param robots Список robots-тегов для страницы.
|
|
8212
|
+
* @param schemaOrgData Данные Schema.org для встраивания на странице.
|
|
7981
8213
|
*/
|
|
7982
|
-
setFromResource(resource, img
|
|
8214
|
+
setFromResource(resource, img, robots, schemaOrgData) {
|
|
7983
8215
|
const updatedResource = {
|
|
7984
8216
|
title: this.replaceTemplates(resource?.title ?? this.defaultSeo.title, resource?.instanceData ?? {}),
|
|
7985
8217
|
description: this.replaceTemplates(resource?.description ?? this.defaultSeo.description, resource?.instanceData ?? {}),
|
|
@@ -7987,21 +8219,24 @@ class ScSeoService {
|
|
|
7987
8219
|
meta: resource?.meta ?? [],
|
|
7988
8220
|
};
|
|
7989
8221
|
const url = `${this.urls.siteUrl}${this.router.url}`;
|
|
7990
|
-
this.setMetaTags(updatedResource.title, updatedResource.description, updatedResource.keywords, updatedResource.meta, robots);
|
|
7991
|
-
this.setOpenGraphMetaTags(updatedResource.title, updatedResource.description, url, img);
|
|
8222
|
+
this.setMetaTags(updatedResource.title, updatedResource.description, updatedResource.keywords, updatedResource.meta, robots ?? ['index', 'follow']);
|
|
8223
|
+
this.setOpenGraphMetaTags(updatedResource.title, updatedResource.description, url, img ?? this.urls.logoUrl);
|
|
7992
8224
|
this.setCanonicalLink(url);
|
|
8225
|
+
// Устанавливаем Schema.org параметры.
|
|
8226
|
+
this.setSchemaOrgData(this.schemaOrgFactory.merge([this.schemaOrgFactory.generateForOrganization(), ...(schemaOrgData ? [schemaOrgData] : [])]));
|
|
7993
8227
|
}
|
|
7994
8228
|
/**
|
|
7995
8229
|
* Устанавливает seo параметры со значением по умолчанию.
|
|
7996
8230
|
*
|
|
7997
8231
|
* @param title Заголовок страницы.
|
|
7998
8232
|
* @param robots Список robots тегов для страницы.
|
|
8233
|
+
* @param schemaOrgData Данные Schema.org для встраивания на странице.
|
|
7999
8234
|
*/
|
|
8000
|
-
setByDefault(title, robots
|
|
8235
|
+
setByDefault(title, robots, schemaOrgData) {
|
|
8001
8236
|
this.setFromResource({
|
|
8002
8237
|
title: `${title ?? this.defaultSeo.title} - ${this.companyInfo.name}`,
|
|
8003
8238
|
description: title ? `${title}, ${this.defaultSeo.description}` : this.defaultSeo.description,
|
|
8004
|
-
}, this.urls.logoUrl, robots);
|
|
8239
|
+
}, this.urls.logoUrl, robots ?? ['index', 'follow'], schemaOrgData);
|
|
8005
8240
|
}
|
|
8006
8241
|
/**
|
|
8007
8242
|
* Устанавливает seo параметры со значением по умолчанию. Устанавливает значение "не индексировать" для поисковых роботов.
|
|
@@ -8032,9 +8267,7 @@ class ScSeoService {
|
|
|
8032
8267
|
this.setOpenGraphMetaTags(title, description, url, this.mediaImageTransformerPipe.transform(item.image, true));
|
|
8033
8268
|
this.setCanonicalLink(url);
|
|
8034
8269
|
// Устанавливаем Schema.org параметры.
|
|
8035
|
-
|
|
8036
|
-
this.setSchemaOrgData(schemaOrgData);
|
|
8037
|
-
}
|
|
8270
|
+
this.setSchemaOrgData(this.schemaOrgFactory.merge([this.schemaOrgFactory.generateForOrganization(), ...(schemaOrgData ? [schemaOrgData] : [])]));
|
|
8038
8271
|
}
|
|
8039
8272
|
/**
|
|
8040
8273
|
* Заменяет шаблоны в тексте на значения из объекта.
|