diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-edit-content-html/models/meta-tags-model.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-edit-content-html/models/meta-tags-model.ts index 4de67f6af956..57ab05e70ffc 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-edit-content-html/models/meta-tags-model.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-edit-content-html/models/meta-tags-model.ts @@ -156,22 +156,26 @@ export const socialMediaTiles: Record = { [SEO_MEDIA_TYPES.FACEBOOK]: { label: 'Facebook', value: SEO_MEDIA_TYPES.FACEBOOK, - icon: 'pi pi-facebook' + icon: 'pi pi-facebook', + description: 'seo.rules.media.preview.tile' }, [SEO_MEDIA_TYPES.TWITTER]: { label: 'X (Formerly Twitter)', value: SEO_MEDIA_TYPES.TWITTER, - icon: 'pi pi-twitter' + icon: 'pi pi-twitter', + description: 'seo.rules.media.preview.tile' }, [SEO_MEDIA_TYPES.LINKEDIN]: { label: 'Linkedin', value: SEO_MEDIA_TYPES.LINKEDIN, - icon: 'pi pi-linkedin' + icon: 'pi pi-linkedin', + description: 'seo.rules.media.preview.tile' }, [SEO_MEDIA_TYPES.GOOGLE]: { label: 'Google', value: SEO_MEDIA_TYPES.GOOGLE, - icon: 'pi pi-google' + icon: 'pi pi-google', + description: 'seo.rules.media.search.engine' } }; @@ -179,4 +183,5 @@ export interface SocialMediaOption { label: string; value: SEO_MEDIA_TYPES; icon: string; + description: string; } diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.spec.ts index e60eb37cec7c..c8c4e5bdef2f 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.spec.ts @@ -107,7 +107,9 @@ describe('DotSetMetaTagsService', () => { 'seo.rules.og-description.found': 'og:description meta tag with valid content found!', 'seo.rules.description.more.one.found': - 'More than 1 Meta Description found!' + 'More than 1 Meta Description found!', + 'seo.rules.og-description.description.not.found': + 'og:description meta tag, and Meta Description not found!' }) }, DotUploadService @@ -266,4 +268,19 @@ describe('DotSetMetaTagsService', () => { done(); }); }); + + it('should og:description meta tag, and Meta Description not found!', (done) => { + const testDoc: Document = document.implementation.createDocument( + 'http://www.w3.org/1999/xhtml', + 'html', + null + ); + + service.getMetaTagsResults(testDoc).subscribe((value) => { + expect(value[5].items[0].message).toEqual( + 'og:description meta tag, and Meta Description not found!' + ); + done(); + }); + }); }); diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.ts index 4ff0d083890f..8c3944867c78 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/html/dot-seo-meta-tags.service.ts @@ -231,6 +231,8 @@ export class DotSeoMetaTagsService { private getOgDescriptionItems(metaTagsObject: SeoMetaTags): SeoRulesResult[] { const result: SeoRulesResult[] = []; const ogDescription = metaTagsObject['og:description']; + const description = metaTagsObject['description']; + const descriptionElements = metaTagsObject['descriptionElements']; const descriptionOgElements = metaTagsObject['descriptionOgElements']; if (descriptionOgElements?.length > 1) { @@ -241,7 +243,22 @@ export class DotSeoMetaTagsService { ); } - if (!ogDescription || descriptionOgElements.length === 0) { + if ( + this.areAllFalsyOrEmpty([ + ogDescription, + descriptionOgElements, + description, + descriptionElements + ]) + ) { + result.push( + this.getErrorItem( + this.dotMessageService.get('seo.rules.og-description.description.not.found') + ) + ); + } + + if (description && this.areAllFalsyOrEmpty([ogDescription, descriptionOgElements])) { result.push( this.getErrorItem(this.dotMessageService.get('seo.rules.og-description.not.found')) ); @@ -391,7 +408,7 @@ export class DotSeoMetaTagsService { if (titleOgElements?.length >= 1 && this.areAllFalsyOrEmpty([titleOg])) { result.push( - this.getErrorItem(this.dotMessageService.get(' seo.rules.og-title.found.empty')) + this.getErrorItem(this.dotMessageService.get('seo.rules.og-title.found.empty')) ); } @@ -444,6 +461,14 @@ export class DotSeoMetaTagsService { ); } + if (imageOgElements?.length >= 1 && this.areAllFalsyOrEmpty([imageOg])) { + result.push( + this.getErrorItem( + this.dotMessageService.get('seo.rules.og-image.more.one.found.empty') + ) + ); + } + if (imageOgElements?.length > 1) { result.push( this.getErrorItem( @@ -516,7 +541,9 @@ export class DotSeoMetaTagsService { if (titleCardElements.length >= 1 && this.areAllFalsyOrEmpty([titleCard])) { result.push( - this.getErrorItem(this.dotMessageService.get('seo.rules.twitter-card-title.empty')) + this.getErrorItem( + this.dotMessageService.get('seo.rules.twitter-card-title.found.empty') + ) ); } diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.html b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.html index adf7869dab0b..b4580f44078f 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.html +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.html @@ -19,10 +19,14 @@ alt="favicon" /> {{ preview.hostName }} - + {{ preview.title }} -

+

{{ preview.description }}

diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.spec.ts index fccd478d6c4d..41ab80ba5857 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.spec.ts @@ -7,7 +7,10 @@ import { MockDotMessageService } from '@dotcms/utils-testing'; import { DotResultsSeoToolComponent } from './dot-results-seo-tool.component'; import { seoOGTagsMock, seoOGTagsResultMock, seoOGTagsResultOgMockTwitter } from './mocks'; -import { SEO_MEDIA_TYPES } from '../../../content/services/dot-edit-content-html/models/meta-tags-model'; +import { + SEO_MEDIA_TYPES, + SEO_LIMITS +} from '../../../content/services/dot-edit-content-html/models/meta-tags-model'; import { DotSeoMetaTagsService } from '../../../content/services/html/dot-seo-meta-tags.service'; describe('DotResultsSeoToolComponent', () => { @@ -126,6 +129,18 @@ describe('DotResultsSeoToolComponent', () => { }); }); + it('should display title', () => { + const titleElement = spectator.query(byTestId('results-seo-tool-search-title')); + expect(titleElement.textContent).toContain(seoOGTagsMock.title); + expect(titleElement.textContent.length).toBeLessThan(SEO_LIMITS.MAX_TITLE_LENGTH); + }); + + it('should display description', () => { + const titleElement = spectator.query(byTestId('results-seo-tool-search-description')); + expect(titleElement.textContent).toContain(seoOGTagsMock.description); + expect(titleElement.textContent.length).toBeLessThan(SEO_LIMITS.MAX_DESCRIPTION_LENGTH); + }); + it('should display host Name', () => { const hostName = spectator.query(byTestId('page-hostName')); expect(hostName).toHaveText(spectator.component.hostName); diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.ts index 5562f3a078bd..977a991a91f5 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-results-seo-tool/dot-results-seo-tool.component.ts @@ -67,11 +67,19 @@ export class DotResultsSeoToolComponent implements OnInit, OnChanges { seoMediaTypes = SEO_MEDIA_TYPES; ngOnInit() { + const title = + this.seoOGTags['og:title']?.slice(0, SEO_LIMITS.MAX_OG_TITLE_LENGTH) || + this.seoOGTags.title?.slice(0, SEO_LIMITS.MAX_TITLE_LENGTH); + + const description = + this.seoOGTags['og:description']?.slice(0, SEO_LIMITS.MAX_OG_DESCRIPTION_LENGTH) || + this.seoOGTags.description?.slice(0, SEO_LIMITS.MAX_DESCRIPTION_LENGTH); + this.allPreview = [ { hostName: this.hostName, - title: this.seoOGTags['og:title']?.slice(0, SEO_LIMITS.MAX_OG_TITLE_LENGTH), - description: this.seoOGTags.description, + title, + description, type: 'Desktop', isMobile: false, image: this.seoOGTags['og:image'], @@ -90,8 +98,8 @@ export class DotResultsSeoToolComponent implements OnInit, OnChanges { }, { hostName: this.hostName, - title: this.seoOGTags['og:title'], - description: this.seoOGTags.description, + title, + description, type: 'Mobile', isMobile: true } diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.html b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.html index 8b95ba02c2d2..5c05af3b58a2 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.html +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.html @@ -8,7 +8,7 @@
{{ socialMediaTiles[socialMedia]?.label }}
{{ - 'seo.rules.media.preview.tile' | dm + socialMediaTiles[socialMedia]?.description | dm }} diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.spec.ts index ae7a3c6296f7..090015623ded 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-select-seo-tool/dot-select-seo-tool.component.spec.ts @@ -15,7 +15,8 @@ describe('DotSelectSeoToolComponent', () => { { provide: DotMessageService, useValue: new MockDotMessageService({ - 'seo.rules.media.preview.tile': 'Social Media Preview Tile' + 'seo.rules.media.preview.tile': 'Social Media Preview Tile', + 'seo.rules.media.search.engine': 'Search Engine Results Page' }) } ], @@ -44,6 +45,20 @@ describe('DotSelectSeoToolComponent', () => { ); }); + it('should display social media preview tile when socialMedia input is provided', () => { + spectator.setInput({ + socialMedia: SEO_MEDIA_TYPES.GOOGLE + }); + spectator.detectChanges(); + + expect(spectator.query(byTestId('select-seo-tool-item'))).toExist(); + expect(spectator.query(byTestId('select-seo-tool-icon'))).toHaveClass('pi-google'); + expect(spectator.query(byTestId('select-seo-tool-title'))).toHaveText('Google'); + expect(spectator.query(byTestId('select-seo-tool-details'))).toHaveText( + 'Search Engine Results Page' + ); + }); + it('should display device preview tile when device input is provided', () => { spectator.setInput({ socialMedia: null diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index 7a836b656eb3..e1912ad96edb 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -5439,7 +5439,8 @@ seo.rules.description.greater=Meta Description found! seo.rules.og-description.more.one.found=more than 1 og:description meta tag found! seo.rules.og-description.found.empty=og:description meta tag found, but is empty! seo.rules.og-description.not.found=og:description meta tag not found! Showing Meta Description instead. -seo.rules.og-description.greater=Meta Description found, but it has more than 160 characters. +seo.rules.og-description.description.not.found=og:description meta tag, and Meta Description not found! +seo.rules.og-description.greater=og:description meta tag found, but it has more than 160 characters. seo.rules.og-description.less=og:description meta tag found, but has fewer than 55 characters of content. seo.rules.og-description.found=og:description meta tag with valid content found! @@ -5476,12 +5477,11 @@ seo.rules.twitter-card.found=twitter:card meta tag found! seo.rules.twitter-card-title.not.found=twitter:title meta tag not found! Showing HTML Title instead. seo.rules.twitter-card-title.more.one.found=more than 1 twitter:title meta tag found! -seo.rules.twitter-card-title.more.one.found.empty=twitter:title meta tag found, but is empty! +seo.rules.twitter-card-title.found.empty=twitter:title meta tag found, but is empty! seo.rules.twitter-card.title.greater=twitter:title meta tag found, but has more than 70 characters. seo.rules.twitter-card.title.less=twitter:title meta tag found, but has fewer than 30 characters of content. seo.rules.twitter-card-title.found=twitter:title meta tag with valid content found! - seo.rules.twitter-card-description.not.found=twitter:description meta tag not found! Showing Description instead. seo.rules.twitter-card-description.more.one.found=more than 1 twitter:description meta tag found! seo.rules.twitter-card-description.more.one.found.empty=twitter:description meta tag found, but is empty! @@ -5516,7 +5516,7 @@ seo.rules.read-more.linkedin.learn=Learn more about Meta Tags: Getting Them Right for LinkedIn. seo.rules.read-more.linkedin.summary=Read more about social media tile image sizes. -seo.rules.read-more.google.favicons=Favicons should be .ico files. +seo.rules.read-more.google.favicons=Favicons should be .ico files. seo.rules.read-more.google.title=HTML Title content should be between 30 and 60 characters. seo.rules.read-more.google.title.unique=HTML Title content should be unique per page across your site. seo.rules.read-more.google.description=Meta Description tags should be under 160 characters. @@ -5525,4 +5525,5 @@ seo.rules.read-more.google.meta-tags=What Are Meta Descriptions And How to Write Them. seo.rules.read-more.google.image-sizes=Read more about social media tile image sizes. +seo.rules.media.search.engine=Search Engine Results Page seo.rules.media.preview.tile=Social Media Preview Tile