Commit badb84f8 authored by Matthieu Benoist's avatar Matthieu Benoist
Browse files

SEO service for title and meta

parent 4f8446c3
......@@ -6,6 +6,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { environment } from '../environments/environment';
import { NavigationHistoryService } from './core/services';
import { AppRoutes } from './routes';
import { SeoSErvice } from './editorialisation/services';
@Component({
selector: 'app-root',
......@@ -19,6 +20,7 @@ export class AppComponent implements OnInit {
private _activatedRoute: ActivatedRoute,
private _titleService: Title,
private _angulartics2Piwik: Angulartics2Piwik,
private _seoSErvice: SeoSErvice,
) {
this._angulartics2Piwik.startTracking();
}
......@@ -47,8 +49,14 @@ export class AppComponent implements OnInit {
return titles;
}),
).subscribe((titles) => {
const title = titles.join(' - ');
this._titleService.setTitle(title);
if (titles.length > 1) {
const title = titles.join(' - ');
this._seoSErvice.setRoutingTitle(title);
}
else {
this._seoSErvice.setRoutingTitle('');
}
});
}
......
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Meta } from '@angular/platform-browser';
import { ActivatedRoute, Router, Scroll } from '@angular/router';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
......@@ -11,6 +10,7 @@ import { AppRoutes } from '../../../routes';
import { IPageHeaderInfo, Metadata, typesMetadata } from '../../../shared/models';
import { isRentertron } from '../../../shared/variables';
import { DatasetDetailService } from '../../services';
import { SeoSErvice } from '../../../editorialisation/services';
@Component({
......@@ -41,7 +41,7 @@ export class DatasetDetailComponent implements OnInit, OnDestroy {
private _router: Router,
private _scroller: ViewportScroller,
private _navigationHistoryService: NavigationHistoryService,
private _meta: Meta,
private _seoService: SeoSErvice,
) { }
ngOnInit() {
......@@ -58,8 +58,6 @@ export class DatasetDetailComponent implements OnInit, OnDestroy {
this._route.params.subscribe((params) => {
// Set the title and description for the research page
this._meta.updateTag({ name: 'description', content: this.metadata.abstract });
this.isLoading = true;
this.initDatasetInfo();
});
......@@ -224,6 +222,8 @@ export class DatasetDetailComponent implements OnInit, OnDestroy {
this.isLoading = false;
this._seoService.setDatasetSEO(this.metadata);
// If a tab (info, data...) is not specified in the url then redirect to the data tab if the dataset
// as a table or a map or to the info tab in the failing case
if (!this._route.snapshot.firstChild) {
......
......@@ -17,44 +17,26 @@ export const routes: Routes = [
{
path: AppRoutes.info.uri,
component: DatasetInfoComponent,
data: {
title: AppRoutes.info.title,
},
},
{
path: AppRoutes.data.uri,
component: DatasetTableMapComponent,
data: {
title: AppRoutes.data.title,
},
},
{
path: AppRoutes.resources.uri,
component: DatasetAPIComponent,
data: {
title: AppRoutes.resources.title,
},
},
{
path: AppRoutes.downloads.uri,
component: DatasetDownloadsComponent,
data: {
title: AppRoutes.downloads.title,
},
},
{
path: AppRoutes.otherResources.uri,
component: DatasetDownloadsComponent,
data: {
title: AppRoutes.otherResources.title,
},
},
{
path: AppRoutes.dataReuses.uri,
component: DatasetReusesComponent,
data: {
title: AppRoutes.dataReuses.title,
},
},
],
},
......
import { Component, OnInit } from '@angular/core';
import { DomSanitizer, Meta, SafeHtml } from '@angular/platform-browser';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { environment } from '../../../../environments/environment';
import { ToolsService } from '../../../core/services/tools.service';
import { AppRoutes } from '../../../routes';
import { IPageHeaderInfo } from '../../../shared/models';
import { CMSContent } from '../../models';
import { EditorialisationService } from '../../services';
import { EditorialisationService, SeoSErvice } from '../../services';
@Component({
selector: 'app-cms-page',
......@@ -26,7 +26,7 @@ export class CMSPageComponent implements OnInit {
private sanitizer: DomSanitizer,
private _toolsService: ToolsService,
private _router: Router,
private _meta: Meta,
private _seoService: SeoSErvice,
) {
}
......@@ -42,14 +42,14 @@ export class CMSPageComponent implements OnInit {
this._editorialisationService.getPage(page).subscribe((page) => {
this.page = page;
// Set the title and description for a CMS page
this._meta.updateTag({ name: 'description', content: this.page.content.excerpt });
if (!(this.page instanceof CMSContent)) {
this._router.navigate(['/', AppRoutes.page404.uri]);
} else {
this.safePageContent = this.sanitizer.bypassSecurityTrustHtml(this._toolsService.decorateRichText(this.page.content.html));
this.pageHeaderInfo.title = this.page.content.title;
this._seoService.setPostSEO(this.page);
}
});
});
......
import { DatePipe } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { DomSanitizer, Meta, SafeHtml } from '@angular/platform-browser';
import { DomSanitizer, Meta, SafeHtml, Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { datasetStatistics, notificationMessages } from '../../../../i18n/traductions';
import { notificationMessages } from '../../../../i18n/traductions';
import { ToolsService } from '../../../core/services/tools.service';
import { ElasticsearchService } from '../../../elasticsearch/services/elasticsearch.service';
import { SeoSErvice } from '../../services/seo.service'
import { AppRoutes } from '../../../routes';
import { IPageHeaderInfo, Metadata, typesMetadata } from '../../../shared/models';
import { CMSContent } from '../../models/cms-content.model';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-cms-post-detail',
......@@ -36,7 +38,9 @@ export class CMSPostDetailComponent implements OnInit {
private _datePipe: DatePipe,
private _sanitizer: DomSanitizer,
private _elasticSearchService: ElasticsearchService,
private _seoService: SeoSErvice,
private _toolsService: ToolsService,
private _titleService: Title,
private _meta: Meta,
) {
}
......@@ -47,7 +51,7 @@ export class CMSPostDetailComponent implements OnInit {
if (this.post) {
// Set the title and description for a CMS post
this._meta.updateTag({ name: 'description', content: this.post.content.excerpt });
this._seoService.setPostSEO(this.post);
this.safePostContent = this._sanitizer.bypassSecurityTrustHtml(this._toolsService.decorateRichText(this.post.content.html));
......
......@@ -3,6 +3,7 @@ import { IPageHeaderInfo } from '../../../shared/models';
import { ActivatedRoute } from '@angular/router';
import { pageTitles, contactTrad } from '../../../../i18n/traductions';
import { AppRoutes } from '../../../routes';
import { SeoSErvice } from '../../services';
@Component({
selector: 'app-contribution',
......@@ -19,10 +20,12 @@ export class ContributionComponent implements OnInit {
constructor(
private _route: ActivatedRoute,
private _seoService: SeoSErvice,
) {
}
ngOnInit() {
this._seoService.setPostSEO()
this._route.data.subscribe((data) => {
this.pageHeaderInfo.hasBetaStyle = data.hasBetaStyle;
this.pageHeaderInfo.title = pageTitles.contribution;
......
......@@ -6,7 +6,7 @@ import { ElasticsearchService } from '../../../elasticsearch/services/elasticsea
import { AppRoutes } from '../../../routes';
import { IPageHeaderInfo, Metadata, typesMetadata } from '../../../shared/models';
import { Reuse } from '../../models';
import { ReusesService } from '../../services';
import { ReusesService, SeoSErvice } from '../../services';
@Component({
selector: 'app-reuse-detail',
......@@ -26,6 +26,7 @@ export class ReuseDetailComponent implements OnInit {
private _reusesService: ReusesService,
private _route: ActivatedRoute,
private _elasticSearchService: ElasticsearchService,
private _seoSErvice: SeoSErvice,
) { }
ngOnInit() {
......@@ -40,6 +41,8 @@ export class ReuseDetailComponent implements OnInit {
return this._elasticSearchService.getDatasetMetadata(slug);
});
this._seoSErvice.setReuseSEO(this.reuse);
forkJoin(calls).pipe(
map((response) => {
return response.map((e, i) => {
......
......@@ -34,6 +34,11 @@ export interface IGhostContentResponse {
featured: boolean;
tags: IGhostTag[];
highlight?: IHighlight[];
meta_description: string;
meta_title?: string;
og_title?: string;
og_description?: string;
og_image?: string;
}
export interface IHighlight {
......@@ -100,6 +105,11 @@ export class GhostContentResponse {
modificationDate: number;
highlight?: string[];
titleHighlight?: string;
meta_description?: string;
meta_title?: string;
og_title?: string;
og_description?: string;
og_image?: string;
constructor(data: IGhostContentResponse) {
this.id = data.id;
......@@ -107,7 +117,15 @@ export class GhostContentResponse {
this.title = (data.title != null) ? data.title : '';
this.html = (data.html != null) ? data.html : '';
this.excerpt = (data.excerpt != null) ? data.excerpt : '';
this.meta_title = (data.meta_title != null) ? data.meta_title: this.title;
this.meta_description = (data.meta_description != null) ? data.meta_description : this.excerpt;
this.og_title=(data.og_title != null) ? data.og_title : this.title;
this.og_description=(data.og_description != null) ? data.og_description : this.excerpt;
this.featureImage = data.feature_image;
this.og_image = this.featureImage;
this.publicationDate = (data.published_at != null) ? Date.parse(data.published_at) : 0;
this.modificationDate = (data.updated_at != null) ? Date.parse(data.updated_at) : 0;
......
......@@ -3,6 +3,7 @@ import { OrganizationsService } from './organizations.service';
import { CreditsService } from './credits.service';
import { ChangelogsService } from './changelog.service';
import { ReusesService } from './reuses.service';
import { SeoSErvice } from './seo.service';
export {
EditorialisationService,
......@@ -10,6 +11,7 @@ export {
CreditsService,
ChangelogsService,
ReusesService,
SeoSErvice,
};
// tslint:disable-next-line:variable-name
......@@ -19,4 +21,5 @@ export const EditorialisationServices = [
CreditsService,
ChangelogsService,
ReusesService,
SeoSErvice,
];
import { Injectable } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';
import { environment } from '../../../environments/environment';
import { AppRoutes } from '../../routes';
import { Metadata } from '../../shared/models';
import { CMSContent, Reuse } from '../models';
@Injectable()
export class SeoSErvice {
private _lang: string;
private _defaultImg: string;
private _defaultDesc: string;
private _defaultTitle: string;
constructor(
private _titleService: Title,
private _metaService: Meta,
) {
this._lang = window.location.href.includes(environment.angularAppHost.en) ? 'en' : 'fr';
this._defaultImg = this._metaService.getTag("property='og:image'").content;
this._defaultDesc = this._metaService.getTag("name='description'").content;
this._defaultTitle = this._titleService.getTitle();
}
setPostSEO(post:CMSContent)
{
const imageUrl = (post.content.og_image != null) ?
post.content.og_image : post.content.featureImage;
const meta = [
{ name: 'description', content: post.content.excerpt },
{ property: 'og:title', content: post.content.og_title },
{ property: 'og:description', content: post.content.og_description },
{ property: 'og:image', content: imageUrl },
];
this._setMeta(meta);
this._setTitle(post.content.title);
}
setDatasetSEO(data: Metadata)
{
const imageUrl = (data.image != null) ?
data.image.url : this._defaultImg;
const meta = [
{ name: 'description', content: data.abstractTroncated },
{ property: 'og:title', content: data.title },
{ property: 'og:description', content: data.abstractTroncated },
{ property: 'og:image', content: imageUrl },
];
this._setMeta(meta);
this._setTitle(data.title);
}
setRoutingTitle(title:string)
{
if (title=='') {
title=this._defaultTitle;
}
const meta = [
{ name: 'description', content: this._defaultDesc },
{ property: 'og:title', content: title },
{ property: 'og:description', content: this._defaultDesc },
{ property: 'og:image', content: this._defaultImg }
];
this._setMeta(meta);
this._titleService.setTitle(title);
}
setReuseSEO (reuse: Reuse)
{
const title:string = reuse.name;
const meta = [
{ name: 'description', content: '' },
{ property: 'og:title', content: title },
{ property: 'og:description', content: '' },
{ property: 'og:image', content: reuse.logo }
];
this._setTitle(title);
this._setMeta(meta);
}
private _setTitle(title:string) {
this._titleService.setTitle(`${title} - ${AppRoutes.root.title[this._lang]}` );
}
private _setMeta(metatags:{name?: string, property?:string, content:string}[]) {
metatags.forEach((meta) => {
if (meta.content!= null) {
this._metaService.updateTag(meta);
}
});
}
}
......@@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<title>Accueil - data.grandlyon.com</title>
<meta name="description" content="Les données des acteurs du territoire de la Métropole de Lyon">
<title>data.grandlyon.com : plateforme open data de la métropole de Lyon</title>
<meta name="description" content="Basé entièrement sur des briques logicielles Open Source, ce portail vous invite à découvrir les données des acteurs du territoire de la Métropole de Lyon">
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment