diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.html b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.html index 213c1148cafe..aebb8f8b6c44 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.html +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.html @@ -40,15 +40,22 @@ *dotShowHideFeature="featureFlagSeo; alternate: disabledComponent" [ngTemplateOutlet]="enabledComponent"> -
+
+
diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.scss b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.scss index 86993f668809..bdb61dc20d73 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.scss +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.scss @@ -2,7 +2,7 @@ @import "mixins"; :host { - background-color: $color-palette-gray-200; + background-color: $white; display: flex; flex-direction: column; width: 100%; @@ -19,7 +19,7 @@ dot-whats-changed, } .dot-edit__page-wrapper { - background-color: $color-palette-gray-200; + background-color: $white; height: 100%; max-width: 100%; padding: $spacing-5; @@ -41,6 +41,10 @@ dot-whats-changed, flex-grow: 1; z-index: 1; padding-right: 80px; + + &.dot-edit-content__preview { + flex-direction: column; + } } .dot-edit__device-wrapper { @@ -53,8 +57,7 @@ dot-whats-changed, .dot-edit__page-wrapper--deviced & { flex-grow: 0; border-radius: 10px; - border: 2px solid #ddd; - box-shadow: 4px 4px 15px $color-palette-black-op-10; + box-shadow: $md-shadow-6; background: $white; background: linear-gradient(135deg, $white 0%, rgba(240, 240, 240, 1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f0f0f0', GradientType=1); diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.spec.ts index 3fc618887b8c..9560f51e26af 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.component.spec.ts @@ -731,19 +731,24 @@ describe('DotEditContentComponent', () => { it('should render in preview mode', fakeAsync(() => { detectChangesForIframeRender(fixture); - + component.isEditMode = false; expect(dotEditContentHtmlService.renderPage).toHaveBeenCalledWith( mockRenderedPageState, jasmine.any(ElementRef) ); + fixture.detectChanges(); + const wrapperEdit = de.query(By.css('[data-testId="edit-content-wrapper"]')); + expect(dotEditContentHtmlService.initEditMode).not.toHaveBeenCalled(); expect(dotEditContentHtmlService.setCurrentPage).toHaveBeenCalledWith( mockRenderedPageState.page ); + expect(wrapperEdit.nativeElement).toHaveClass('dot-edit-content__preview'); })); it('should render in edit mode', fakeAsync(() => { spyOn(dotLicenseService, 'isEnterprise').and.returnValue(of(true)); + component.isEditMode = true; const state = new DotPageRenderState( mockUser(), new DotPageRender({ @@ -761,7 +766,9 @@ describe('DotEditContentComponent', () => { content: state }); detectChangesForIframeRender(fixture); + fixture.detectChanges(); + const wrapperEdit = de.query(By.css('[data-testId="edit-content-wrapper"]')); expect(dotEditContentHtmlService.initEditMode).toHaveBeenCalledWith( state, jasmine.any(ElementRef) @@ -770,6 +777,7 @@ describe('DotEditContentComponent', () => { expect(dotEditContentHtmlService.setCurrentPage).toHaveBeenCalledWith( state.page ); + expect(wrapperEdit.nativeElement).not.toHaveClass('dot-edit-content__preview'); })); it('should show/hide content palette in edit mode with correct content', fakeAsync(() => { diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.module.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.module.ts index 7c53117c0ec1..98641053b3d1 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.module.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/dot-edit-content.module.ts @@ -44,6 +44,7 @@ import { DotSeoMetaTagsService } from './services/html/dot-seo-meta-tags.service import { DotEditPageToolbarSeoComponent } from '../seo/components/dot-edit-page-toolbar-seo/dot-edit-page-toolbar-seo.component'; import { DotResultsSeoToolComponent } from '../seo/components/dot-results-seo-tool/dot-results-seo-tool.component'; +import { DotSelectSeoToolComponent } from '../seo/components/dot-select-seo-tool/dot-select-seo-tool.component'; const routes: Routes = [ { component: DotEditContentComponent, @@ -75,7 +76,8 @@ const routes: Routes = [ DotIconModule, DotEditPageToolbarSeoComponent, DotShowHideFeatureDirective, - DotResultsSeoToolComponent + DotResultsSeoToolComponent, + DotSelectSeoToolComponent ], exports: [DotEditContentComponent], providers: [ 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 9f8d0bab5326..4de67f6af956 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 @@ -25,14 +25,18 @@ export enum SEO_LIMITS { MIN_TITLE_LENGTH = 30, MAX_TITLE_LENGTH = 60, MIN_OG_TITLE_LENGTH = 30, - MAX_OG_TITLE_LENGTH = 160, + MAX_OG_TITLE_LENGTH = 60, MIN_OG_DESCRIPTION_LENGTH = 55, MAX_OG_DESCRIPTION_LENGTH = 150, + MIN_DESCRIPTION_LENGTH = 55, + MAX_DESCRIPTION_LENGTH = 160, MAX_FAVICONS = 1, MAX_TITLES = 1, MAX_IMAGE_BYTES = 8000000, MAX_TWITTER_IMAGE_BYTES = 5000000, - MAX_TWITTER_DESCRIPTION_LENGTH = 200 + MAX_TWITTER_DESCRIPTION_LENGTH = 200, + MIN_TWITTER_TITLE_LENGTH = 30, + MAX_TWITTER_TITLE_LENGTH = 70 } export enum SEO_RULES_COLORS { @@ -59,7 +63,6 @@ export interface SeoMetaTagsResult { keyColor: string; items: SeoRulesResult[]; sort: number; - info?: string; } export interface SeoMetaTags { @@ -71,6 +74,7 @@ export interface SeoMetaTags { imageOgElements?: NodeListOf; descriptionOgElements?: NodeListOf; description?: string; + descriptionElements?: NodeListOf; 'og:description'?: string; 'og:image'?: string; 'og:title'?: string; @@ -93,7 +97,7 @@ export const SeoMediaKeys = { SEO_OPTIONS.TWITTER_DESCRIPTION, SEO_OPTIONS.TWITTER_IMAGE ], - linkedin: [SEO_OPTIONS.DESCRIPTION, SEO_OPTIONS.OG_IMAGE, SEO_OPTIONS.OG_TITLE], + linkedin: [SEO_OPTIONS.OG_DESCRIPTION, SEO_OPTIONS.OG_IMAGE, SEO_OPTIONS.OG_TITLE], all: [ SEO_OPTIONS.DESCRIPTION, SEO_OPTIONS.OG_IMAGE, @@ -116,7 +120,6 @@ export interface ImageMetaData { export interface OpenGraphOptions { getItems: (object: SeoMetaTags) => Observable; sort: number; - info: string; } export interface MetaTagsPreview { @@ -148,3 +151,32 @@ export const SEO_TAGS = [ SEO_OPTIONS.TWITTER_DESCRIPTION, SEO_OPTIONS.TWITTER_IMAGE ]; + +export const socialMediaTiles: Record = { + [SEO_MEDIA_TYPES.FACEBOOK]: { + label: 'Facebook', + value: SEO_MEDIA_TYPES.FACEBOOK, + icon: 'pi pi-facebook' + }, + [SEO_MEDIA_TYPES.TWITTER]: { + label: 'X (Formerly Twitter)', + value: SEO_MEDIA_TYPES.TWITTER, + icon: 'pi pi-twitter' + }, + [SEO_MEDIA_TYPES.LINKEDIN]: { + label: 'Linkedin', + value: SEO_MEDIA_TYPES.LINKEDIN, + icon: 'pi pi-linkedin' + }, + [SEO_MEDIA_TYPES.GOOGLE]: { + label: 'Google', + value: SEO_MEDIA_TYPES.GOOGLE, + icon: 'pi pi-google' + } +}; + +export interface SocialMediaOption { + label: string; + value: SEO_MEDIA_TYPES; + icon: 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 87e58543de9c..e60eb37cec7c 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 @@ -39,29 +39,25 @@ describe('DotSetMetaTagsService', () => { 'HTML Title found, with an appropriate amount of content!', 'seo.resuls.tool.read.more': 'Read More', 'seo.resuls.tool.version': 'Version', - 'seo.rules.description.info': - "The length of the description allowed will depend on the reader's device size; on the smallest size only about 110 characters are allowed.", - 'seo.rules.title.info': - 'HTML Title content should be between 30 and 60 characters.', 'seo.rules.og-title.not.found': - 'og:title metatag not found! Showing HTML Title instead.', - 'seo.rules.og-title.more.one.found': 'more than 1 og:title metatag found!', + 'og:title meta tag not found! Showing HTML Title instead.', + 'seo.rules.og-title.more.one.found': 'more than 1 og:title meta tag found!', 'seo.rules.og-title.more.one.found.empty': - 'og:title metatag found, but is empty!', + 'og:title meta tag found, but is empty!', 'seo.rules.og-title.greater': - 'og:title metatag found, but has more than 160 characters.', + 'og:title meta tag found, but has more than 160 characters.', 'seo.rules.og-title.less': - 'title metatag found, but has fewer than 30 characters of content.', + 'title meta tag found, but has fewer than 30 characters of content.', 'seo.rules.og-title.found': - 'og:title metatag found, with an appropriate amount of content!', - 'seo.rules.og-image.not.found': 'og:image metatag not found!', - 'seo.rules.og-image.more.one.found': 'more than 1 og:image metatag found!', + 'og:title meta tag found, with an appropriate amount of content!', + 'seo.rules.og-image.not.found': 'og:image meta tag not found!', + 'seo.rules.og-image.more.one.found': 'more than 1 og:image meta tag found!', 'seo.rules.og-image.more.one.found.empty': - 'og:image metatag found, but is empty!', + 'og:image meta tag found, but is empty!', 'seo.rules.og-image.over': - 'og:image metatag found, but image is over 8 MB.', + 'og:image meta tag found, but image is over 8 MB.', 'seo.rules.og-image.found': - 'og:image metatag found, with an appropriate sized image!', + 'og:image meta tag found, with an appropriate sized image!', 'seo.rules.twitter-card.not.found': 'twitter:card meta tag not found!', 'seo.rules.twitter-card.more.one.found': 'more than 1 twitter:card meta tag found!', @@ -109,7 +105,9 @@ describe('DotSetMetaTagsService', () => { '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!' + 'og:description meta tag with valid content found!', + 'seo.rules.description.more.one.found': + 'More than 1 Meta Description found!' }) }, DotUploadService @@ -172,7 +170,7 @@ describe('DotSetMetaTagsService', () => { const twitterDescriptionElements = testDoc.querySelectorAll( 'meta[name="twitter:description"]' ); - + const descriptionElements = testDoc.querySelectorAll('meta[name="description"]'); const descriptionOgElements = testDoc.querySelectorAll('meta[property="og:description"]'); expect(service.getMetaTags(testDoc)).toEqual({ @@ -190,7 +188,8 @@ describe('DotSetMetaTagsService', () => { twitterTitleElements, twitterImageElements, twitterDescriptionElements, - descriptionOgElements + descriptionOgElements, + descriptionElements }); }); @@ -202,7 +201,7 @@ describe('DotSetMetaTagsService', () => { }); }); - it('should that got more than one og-description error', (done) => { + it('should get more than one og-description error', (done) => { const ogMetaDescription = document.createElement('meta'); ogMetaDescription.setAttribute('property', 'og:description'); ogMetaDescription.setAttribute('content', 'BE'); @@ -215,17 +214,17 @@ describe('DotSetMetaTagsService', () => { testDoc.head.appendChild(ogMetaDescriptionSecond); service.getMetaTagsResults(testDoc).subscribe((value) => { - expect(value[0].items[0].message).toEqual( + expect(value[5].items[0].message).toEqual( 'more than 1 og:description meta tag found!' ); - expect(value[0].items[1].message).toEqual( + expect(value[5].items[1].message).toEqual( 'og:description meta tag found, but has fewer than 55 characters of content.' ); done(); }); }); - it('should that got more than one og:title error', (done) => { + it('should get more than one og:title error', (done) => { const ogMetaTitle = document.createElement('meta'); ogMetaTitle.setAttribute('property', 'og:title'); ogMetaTitle.setAttribute('content', 'Costa Rica Special Offer'); @@ -239,12 +238,32 @@ describe('DotSetMetaTagsService', () => { service.getMetaTagsResults(testDoc).subscribe((value) => { expect(value[2].items[0].message).toEqual( - 'more than 1 og:title metatag found!' + 'more than 1 og:title meta tag found!' ); expect(value[2].items[1].message).toEqual( - 'title metatag found, but has fewer than 30 characters of content.' + 'title meta tag found, but has fewer than 30 characters of content.' ); done(); }); }); + + it('should get more than description error', (done) => { + const description = document.createElement('meta'); + description.setAttribute('name', 'description'); + description.setAttribute('content', 'Costa Rica Special Offer'); + + testDoc.head.appendChild(description); + + service.getMetaTagsResults(testDoc).subscribe((value) => { + expect(value[0].items[0].message).toEqual('More than 1 Meta Description found!'); + done(); + }); + }); + + it('should get description found', (done) => { + service.getMetaTagsResults(testDoc).subscribe((value) => { + expect(value[0].items[0].message).toEqual('Meta Description 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 c0a27ef564d9..4ff0d083890f 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 @@ -60,6 +60,7 @@ export class DotSeoMetaTagsService { const descriptionOgElements = pageDocument.querySelectorAll( 'meta[property="og:description"]' ); + const descriptionElements = pageDocument.querySelectorAll('meta[name="description"]'); const twitterCardElements = pageDocument.querySelectorAll('meta[name="twitter:card"]'); const twitterTitleElements = pageDocument.querySelectorAll('meta[name="twitter:title"]'); const twitterImageElements = pageDocument.querySelectorAll('meta[name="twitter:image"]'); @@ -78,6 +79,7 @@ export class DotSeoMetaTagsService { metaTagsObject['twitterDescriptionElements'] = twitterDescriptionElements; metaTagsObject['twitterImageElements'] = twitterImageElements; metaTagsObject['descriptionOgElements'] = descriptionOgElements; + metaTagsObject['descriptionElements'] = descriptionElements; return metaTagsObject; } @@ -105,8 +107,7 @@ export class DotSeoMetaTagsService { keyIcon: keysValues.keyIcon, keyColor: keysValues.keyColor, items: items, - sort: ogMap[key]?.sort, - info: ogMap[key]?.info + sort: ogMap[key]?.sort }; }); }) @@ -119,59 +120,49 @@ export class DotSeoMetaTagsService { return { [SEO_OPTIONS.FAVICON]: { getItems: (metaTagsObject: SeoMetaTags) => of(this.getFaviconItems(metaTagsObject)), - sort: 1, - info: '' + sort: 1 }, [SEO_OPTIONS.DESCRIPTION]: { getItems: (metaTagsObject: SeoMetaTags) => of(this.getDescriptionItems(metaTagsObject)), - sort: 3, - info: this.dotMessageService.get('seo.rules.description.info') + sort: 3 }, [SEO_OPTIONS.OG_DESCRIPTION]: { getItems: (metaTagsObject: SeoMetaTags) => - of(this.getDescriptionItems(metaTagsObject)), - sort: 4, - info: this.dotMessageService.get('seo.rules.description.info') + of(this.getOgDescriptionItems(metaTagsObject)), + sort: 4 }, [SEO_OPTIONS.TITLE]: { getItems: (metaTagsObject: SeoMetaTags) => of(this.getTitleItems(metaTagsObject)), - sort: 2, - info: this.dotMessageService.get('seo.rules.title.info') + sort: 2 }, [SEO_OPTIONS.OG_TITLE]: { getItems: (metaTagsObject: SeoMetaTags) => of(this.getOgTitleItems(metaTagsObject)), - sort: 2, - info: this.dotMessageService.get('seo.rules.title.info') + sort: 2 }, [SEO_OPTIONS.OG_IMAGE]: { getItems: (metaTagsObject: SeoMetaTags) => this.getOgImagesItems(metaTagsObject), - sort: 6, - info: '' + sort: 6 }, [SEO_OPTIONS.TWITTER_CARD]: { getItems: (metaTagsObject: SeoMetaTags) => of(this.getTwitterCardItems(metaTagsObject)), - sort: 2, - info: '' + sort: 2 }, [SEO_OPTIONS.TWITTER_TITLE]: { getItems: (metaTagsObject: SeoMetaTags) => of(this.getTwitterTitleItems(metaTagsObject)), - sort: 1, - info: '' + sort: 1 }, [SEO_OPTIONS.TWITTER_DESCRIPTION]: { getItems: (metaTagsObject: SeoMetaTags) => of(this.getTwitterDescriptionItems(metaTagsObject)), - sort: 3, - info: '' + sort: 3 }, [SEO_OPTIONS.TWITTER_IMAGE]: { getItems: (metaTagsObject: SeoMetaTags) => this.getTwitterImageItems(metaTagsObject), - sort: 4, - info: '' + sort: 4 } }; } @@ -199,7 +190,9 @@ export class DotSeoMetaTagsService { const faviconElements = metaTagsObject['faviconElements']; if (faviconElements.length === 0) { - items.push(); + items.push( + this.getErrorItem(this.dotMessageService.get('seo.rules.favicon.not.found')) + ); } if (faviconElements.length > SEO_LIMITS.MAX_FAVICONS) { @@ -235,10 +228,9 @@ export class DotSeoMetaTagsService { }; } - private getDescriptionItems(metaTagsObject: SeoMetaTags): SeoRulesResult[] { + private getOgDescriptionItems(metaTagsObject: SeoMetaTags): SeoRulesResult[] { const result: SeoRulesResult[] = []; const ogDescription = metaTagsObject['og:description']; - const description = metaTagsObject['description']; const descriptionOgElements = metaTagsObject['descriptionOgElements']; if (descriptionOgElements?.length > 1) { @@ -249,7 +241,7 @@ export class DotSeoMetaTagsService { ); } - if (!ogDescription && description) { + if (!ogDescription || descriptionOgElements.length === 0) { result.push( this.getErrorItem(this.dotMessageService.get('seo.rules.og-description.not.found')) ); @@ -288,15 +280,71 @@ export class DotSeoMetaTagsService { return result; } + private getDescriptionItems(metaTagsObject: SeoMetaTags): SeoRulesResult[] { + const result: SeoRulesResult[] = []; + const description = metaTagsObject['description']; + const descriptionElements = metaTagsObject['descriptionElements']; + + if (descriptionElements?.length > 1) { + result.push( + this.getErrorItem( + this.dotMessageService.get('seo.rules.description.more.one.found') + ) + ); + } + + if (this.areAllFalsyOrEmpty([description, descriptionElements])) { + result.push( + this.getErrorItem(this.dotMessageService.get('seo.rules.description.not.found')) + ); + } + + if (descriptionElements.length >= 1 && this.areAllFalsyOrEmpty([description])) { + result.push( + this.getErrorItem(this.dotMessageService.get('seo.rules.description.found.empty')) + ); + } + + if (description?.length < SEO_LIMITS.MIN_DESCRIPTION_LENGTH) { + result.push( + this.getWarningItem(this.dotMessageService.get('seo.rules.description.less')) + ); + } + + if (description?.length > SEO_LIMITS.MAX_DESCRIPTION_LENGTH) { + result.push( + this.getWarningItem(this.dotMessageService.get('seo.rules.description.greater')) + ); + } + + if ( + description && + description?.length > SEO_LIMITS.MIN_OG_DESCRIPTION_LENGTH && + description?.length < SEO_LIMITS.MAX_OG_DESCRIPTION_LENGTH + ) { + result.push( + this.getDoneItem(this.dotMessageService.get('seo.rules.description.found')) + ); + } + + return result; + } + private getTitleItems(metaTagsObject: SeoMetaTags): SeoRulesResult[] { const result: SeoRulesResult[] = []; const title = metaTagsObject['title']; const titleElements = metaTagsObject['titleElements']; - if (!titleElements) { + if (this.areAllFalsyOrEmpty([title, titleElements])) { result.push(this.getErrorItem(this.dotMessageService.get('seo.rules.title.not.found'))); } + if (titleElements?.length >= 1 && this.areAllFalsyOrEmpty([title])) { + result.push( + this.getErrorItem(this.dotMessageService.get('seo.rules.title.found.empty')) + ); + } + if (titleElements?.length > 1) { result.push( this.getErrorItem(this.dotMessageService.get('seo.rules.title.more.one.found')) @@ -325,10 +373,26 @@ export class DotSeoMetaTagsService { private getOgTitleItems(metaTagsObject: SeoMetaTags): SeoRulesResult[] { const result: SeoRulesResult[] = []; const titleOgElements = metaTagsObject['titleOgElements']; + const titleElements = metaTagsObject['titleElements']; const titleOg = metaTagsObject['og:title']; + const title = metaTagsObject['title']; + + if (title && this.areAllFalsyOrEmpty([titleOg, titleOgElements])) { + result.push( + this.getErrorItem(this.dotMessageService.get('seo.rules.og-title.not.found')) + ); + } + + if (this.areAllFalsyOrEmpty([title, titleOg, titleElements, titleOgElements])) { + result.push( + this.getErrorItem(this.dotMessageService.get('seo.rules.og-title.not.found.title')) + ); + } - if (!titleOgElements) { - result.push(this.getErrorItem(this.dotMessageService.get('seo.rules.image.not.found'))); + if (titleOgElements?.length >= 1 && this.areAllFalsyOrEmpty([titleOg])) { + result.push( + this.getErrorItem(this.dotMessageService.get(' seo.rules.og-title.found.empty')) + ); } if (titleOgElements?.length > 1) { @@ -372,7 +436,7 @@ export class DotSeoMetaTagsService { ); } - if (!imageOgElements) { + if (this.areAllFalsyOrEmpty([imageOgElements, imageOg])) { result.push( this.getErrorItem( this.dotMessageService.get('seo.rules.og-image.not.found') @@ -395,32 +459,32 @@ export class DotSeoMetaTagsService { private getTwitterCardItems(metaTagsObject: SeoMetaTags): SeoRulesResult[] { const result: SeoRulesResult[] = []; - const titleCardElements = metaTagsObject['twitterCardElements']; - const titleCard = metaTagsObject['twitter:card']; + const twitterCardElements = metaTagsObject['twitterCardElements']; + const twitterCard = metaTagsObject['twitter:card']; - if (titleCardElements.length === 0) { + if (this.areAllFalsyOrEmpty([twitterCard, twitterCardElements])) { result.push( this.getErrorItem(this.dotMessageService.get('seo.rules.twitter-card.not.found')) ); } - if (titleCardElements?.length > 1) { + if (twitterCardElements.length >= 1 && this.areAllFalsyOrEmpty([twitterCard])) { result.push( this.getErrorItem( - this.dotMessageService.get('seo.rules.twitter-card.more.one.found') + this.dotMessageService.get('seo.rules.twitter-card.more.one.found.empty') ) ); } - if (titleCard && titleCard.length === 0) { + if (twitterCardElements?.length > 1) { result.push( this.getErrorItem( - this.dotMessageService.get('seo.rules.twitter-card.more.one.found.empty') + this.dotMessageService.get('seo.rules.twitter-card.more.one.found') ) ); } - if (titleCard) { + if (twitterCard) { result.push( this.getDoneItem(this.dotMessageService.get('seo.rules.twitter-card.found')) ); @@ -434,7 +498,7 @@ export class DotSeoMetaTagsService { const titleCardElements = metaTagsObject['twitterTitleElements']; const titleCard = metaTagsObject['twitter:title']; - if (titleCardElements.length === 0) { + if (this.areAllFalsyOrEmpty([titleCard, titleCardElements])) { result.push( this.getErrorItem( this.dotMessageService.get('seo.rules.twitter-card-title.not.found') @@ -450,13 +514,31 @@ export class DotSeoMetaTagsService { ); } - if (titleCard && titleCard.length === 0) { + if (titleCardElements.length >= 1 && this.areAllFalsyOrEmpty([titleCard])) { result.push( this.getErrorItem(this.dotMessageService.get('seo.rules.twitter-card-title.empty')) ); } - if (titleCard) { + if (titleCard?.length < SEO_LIMITS.MIN_TWITTER_TITLE_LENGTH) { + result.push( + this.getWarningItem(this.dotMessageService.get('seo.rules.twitter-card.title.less')) + ); + } + + if (titleCard?.length > SEO_LIMITS.MAX_TWITTER_TITLE_LENGTH) { + result.push( + this.getWarningItem( + this.dotMessageService.get('seo.rules.twitter-card.title.greater') + ) + ); + } + + if ( + titleCard && + titleCard?.length > SEO_LIMITS.MIN_TWITTER_TITLE_LENGTH && + titleCard?.length < SEO_LIMITS.MAX_TWITTER_TITLE_LENGTH + ) { result.push( this.getDoneItem(this.dotMessageService.get('seo.rules.twitter-card-title.found')) ); @@ -470,7 +552,7 @@ export class DotSeoMetaTagsService { const twitterDescriptionElements = metaTagsObject['twitterDescriptionElements']; const twitterDescription = metaTagsObject['twitter:description']; - if (twitterDescriptionElements.length === 0) { + if (this.areAllFalsyOrEmpty([twitterDescription, twitterDescriptionElements])) { result.push( this.getErrorItem( this.dotMessageService.get('seo.rules.twitter-card-description.not.found') @@ -486,7 +568,10 @@ export class DotSeoMetaTagsService { ); } - if (twitterDescription && twitterDescription.length === 0) { + if ( + twitterDescriptionElements.length >= 1 && + this.areAllFalsyOrEmpty([twitterDescription]) + ) { result.push( this.getErrorItem( this.dotMessageService.get( @@ -501,7 +586,7 @@ export class DotSeoMetaTagsService { twitterDescription.length > SEO_LIMITS.MAX_TWITTER_DESCRIPTION_LENGTH ) { result.push( - this.getErrorItem( + this.getWarningItem( this.dotMessageService.get('seo.rules.twitter-card-description.greater') ) ); @@ -537,7 +622,7 @@ export class DotSeoMetaTagsService { ); } - if (twitterImageElements.length === 0) { + if (this.areAllFalsyOrEmpty([twitterImage, twitterImageElements])) { result.push( this.getErrorItem( this.dotMessageService.get('seo.rules.twitter-image.not.found') @@ -564,6 +649,12 @@ export class DotSeoMetaTagsService { ); } + private areAllFalsyOrEmpty(values: (string | NodeListOf)[]): boolean { + return values.every( + (value) => value === null || value === undefined || value?.length === 0 + ); + } + private getErrorItem(message: string): SeoRulesResult { return { message: this.addHTMLTag(message), @@ -608,7 +699,6 @@ export class DotSeoMetaTagsService { [SEO_MEDIA_TYPES.TWITTER]: [ this.dotMessageService.get('seo.rules.read-more.twitter.learn'), this.dotMessageService.get('seo.rules.read-more.twitter.suggest'), - this.dotMessageService.get('seo.rules.read-more.twitter.twitter-card'), this.dotMessageService.get('seo.rules.read-more.twitter.twitter-title'), this.dotMessageService.get('seo.rules.read-more.twitter.twitter-title.content'), this.dotMessageService.get('seo.rules.read-more.twitter.length'), diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-device-selector-seo/dot-device-selector-seo.component.html b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-device-selector-seo/dot-device-selector-seo.component.html index 609554f92301..8e890d2fb243 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-device-selector-seo/dot-device-selector-seo.component.html +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/seo/components/dot-device-selector-seo/dot-device-selector-seo.component.html @@ -66,7 +66,7 @@

*ngFor="let tile of socialMediaTiles">