Skip to content

Commit

Permalink
feat: add metadata to pages
Browse files Browse the repository at this point in the history
  • Loading branch information
valerymelou committed Jul 7, 2024
1 parent 5d7d50a commit 5d98a7e
Show file tree
Hide file tree
Showing 22 changed files with 315 additions and 12 deletions.
Empty file removed apps/website/src/assets/.gitkeep
Empty file.
4 changes: 2 additions & 2 deletions apps/website/src/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en" class="dark scroll-smooth">
<head>
<meta charset="utf-8" />
Expand All @@ -18,7 +18,7 @@
/>
</head>
<body
class="font-body tracking-wide leading-relaxed dark:text-slate-400 text-slate-900 bg-white dark:bg-dark"
class="font-body dark:bg-dark bg-white leading-relaxed tracking-wide text-slate-900 dark:text-slate-400"
>
<app-root></app-root>
</body>
Expand Down
7 changes: 7 additions & 0 deletions libs/blog/feature-home/src/lib/blog-home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@valerymelou/blob/data-access';
import { LinkComponent } from '@valerymelou/shared/ui';
import { RouterModule } from '@angular/router';
import { MetadataService } from '@valerymelou/shared/seo';

@Component({
selector: 'blog-blog-home',
Expand All @@ -23,12 +24,18 @@ export class BlogHomeComponent implements OnInit {
tags$!: Observable<Results<Tag>>;

ngOnInit(): void {
this.metadataService.updateMetadata({
title: 'Inside my head | Valery Melou',
description:
'I talk about Django, Angular... Web Development in general and many other topics. These are just a few of the things in my head.',
});
this.loadArticles();
this.loadTags();
}

constructor(
private articleService: ArticleService,
private metadataService: MetadataService,
private tagService: TagService,
) {}

Expand Down
15 changes: 13 additions & 2 deletions libs/pages/about/src/lib/about.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MetadataService } from '@valerymelou/shared/seo';

@Component({
selector: 'app-about',
standalone: true,
imports: [CommonModule],
templateUrl: './about.component.html',
})
export class AboutComponent {}
export class AboutComponent implements OnInit {
constructor(private metadataService: MetadataService) {}

ngOnInit(): void {
this.metadataService.updateMetadata({
title: 'About myself (Valery Melou)',
description:
"I'm now specialized into web development. Building RESTfull APIs with Django and Python then, consuming those APIs with Angular and Typescript.",
});
}
}
2 changes: 1 addition & 1 deletion libs/pages/home/src/lib/home.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ <h2 class="text-xl leading-10 lg:text-2xl">
mobile.
</h2>
<div>
<a href="/work" ui-flat-button color="accent" large>Check my work</a>
<a routerLink="/about" ui-flat-button color="accent" large>Learn more</a>
</div>
</div>
</div>
18 changes: 15 additions & 3 deletions libs/pages/home/src/lib/home.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from '@valerymelou/shared/ui';
import { MetadataService } from '@valerymelou/shared/seo';
import { RouterModule } from '@angular/router';

@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule, ButtonComponent],
imports: [CommonModule, RouterModule, ButtonComponent],
templateUrl: './home.component.html',
styles: ':host {display: flex; flex-direction: column; flex: 1;}',
})
export class HomeComponent {}
export class HomeComponent implements OnInit {
constructor(private metadataService: MetadataService) {}

ngOnInit(): void {
this.metadataService.updateMetadata({
title: 'Home of Valery Melou',
description:
'I build beautiful, interactive and accessible experiences for web and mobile.',
});
}
}
2 changes: 1 addition & 1 deletion libs/pages/projects/src/lib/projects.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<h1
class="mb-10 text-2xl font-bold leading-[3rem] text-black lg:text-4xl dark:text-white"
>
Some things I've built
Some of the things I've built
</h1>

<ul>
Expand Down
14 changes: 12 additions & 2 deletions libs/pages/projects/src/lib/projects.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MetadataService } from '@valerymelou/shared/seo';

@Component({
selector: 'app-projects',
standalone: true,
imports: [CommonModule],
templateUrl: './projects.component.html',
})
export class ProjectsComponent {}
export class ProjectsComponent implements OnInit {
constructor(private metadataService: MetadataService) {}

ngOnInit(): void {
this.metadataService.updateMetadata({
title: 'Some of the things I have built | Valery Melou',
description: 'Here are some of the projects I have worked on.',
});
}
}
36 changes: 36 additions & 0 deletions libs/shared/seo/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}
7 changes: 7 additions & 0 deletions libs/shared/seo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# shared-seo

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test shared-seo` to execute the unit tests.
22 changes: 22 additions & 0 deletions libs/shared/seo/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'shared-seo',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/shared/seo',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
20 changes: 20 additions & 0 deletions libs/shared/seo/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "shared-seo",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/shared/seo/src",
"prefix": "app",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/shared/seo/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}
1 change: 1 addition & 0 deletions libs/shared/seo/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/metadata.service';
11 changes: 11 additions & 0 deletions libs/shared/seo/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PageMetadata } from './page-metadata';

export const APP_NAME = 'Valery Melou';
export const DEFAULT_METADATA: PageMetadata = {
title: '',
description: 'Web developer from Yaounde, Cameroon',
keywords: ['angular', 'django', 'angular', 'web', 'developer', 'yaounde'],
type: 'website',
image: '/assets/images/logo.png',
imageAlt: APP_NAME,
};
20 changes: 20 additions & 0 deletions libs/shared/seo/src/lib/metadata.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TestBed } from '@angular/core/testing';

import { MetadataService } from './metadata.service';

describe('MetadataService', () => {
let service: MetadataService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MetadataService);
});

it('should be created', () => {
service.updateMetadata({
title: 'Welcome',
description: 'Welcome',
});
expect(service).toBeTruthy();
});
});
66 changes: 66 additions & 0 deletions libs/shared/seo/src/lib/metadata.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Injectable } from '@angular/core';
import { Meta, MetaDefinition, Title } from '@angular/platform-browser';
import { PageMetadata } from './page-metadata';
import { DEFAULT_METADATA } from './constants';

@Injectable({
providedIn: 'root',
})
export class MetadataService {
constructor(
private meta: Meta,
private title: Title,
) {}

updateMetadata(metadata: Partial<PageMetadata>, index = true): void {
const pageMetadata: PageMetadata = { ...DEFAULT_METADATA, ...metadata };
const metaTags: MetaDefinition[] = [
{ name: 'robots', content: index ? 'index, follow' : 'noindex' },
...this.generateMetaDefinitions(pageMetadata),
];

metaTags.forEach((tag: MetaDefinition) => {
if (tag.content) {
this.meta.updateTag(tag);
}
});

this.title.setTitle(`${metadata.title}`);
}

private generateMetaDefinitions(metadata: PageMetadata): MetaDefinition[] {
return [
// Standard
{ name: 'title', content: metadata.title },
{ name: 'description', content: metadata.description },
{ name: 'keywords', content: metadata.keywords.join(', ') },
// og
...this.generateOgMetaDefinitions(metadata),
// Twitter
...this.generateXMetaDefinitions(metadata),
];
}

private generateOgMetaDefinitions(metadata: PageMetadata): MetaDefinition[] {
return [
{ name: 'og:url', content: window.location.href },
{ property: 'og:title', content: metadata.title },
{ property: 'og:description', content: metadata.description },
{ property: 'og:type', content: metadata.type },
{ property: 'og:image', content: metadata.image },
{ property: 'og:image:secure_url', content: metadata.image },
{ property: 'og:image:alt', content: metadata.imageAlt },
];
}

private generateXMetaDefinitions(metadata: PageMetadata): MetaDefinition[] {
return [
{ name: 'twitter:card', content: 'summary' },
{ name: 'twitter:site', content: '@valerymelou' },
{ name: 'twitter:title', content: metadata.title },
{ name: 'twitter:description', content: metadata.description },
{ name: 'twitter:image', content: metadata.image },
{ name: 'twitter:image:alt', content: metadata.imageAlt },
];
}
}
8 changes: 8 additions & 0 deletions libs/shared/seo/src/lib/page-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface PageMetadata {
title: string;
description: string;
keywords: string[];
type: 'website';
image: string;
imageAlt: string;
}
8 changes: 8 additions & 0 deletions libs/shared/seo/src/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment
globalThis.ngJest = {
testEnvironmentOptions: {
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
},
};
import 'jest-preset-angular/setup-jest';
29 changes: 29 additions & 0 deletions libs/shared/seo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": false,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
18 changes: 18 additions & 0 deletions libs/shared/seo/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"module": "commonjs",
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
"jest.config.ts",
"src/**/*.test.ts"
],
"include": ["src/**/*.ts"]
}
Loading

0 comments on commit 5d98a7e

Please sign in to comment.