Skip to content

Commit

Permalink
feat: init article page
Browse files Browse the repository at this point in the history
  • Loading branch information
valerymelou committed Jul 12, 2024
1 parent 8600cd2 commit cb28067
Show file tree
Hide file tree
Showing 20 changed files with 285 additions and 7 deletions.
8 changes: 8 additions & 0 deletions apps/website/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export const appRoutes: Route[] = [
import('@valerymelou/blog/home').then((c) => c.BlogHomeComponent),
data: { animation: 'BlogHomePage' },
},
{
path: 'blog/:slug',
loadComponent: () =>
import('@valerymelou/blog/article').then(
(c) => c.BlogArticleComponent,
),
data: { animation: 'BlogHomePage' },
},
],
},
];
16 changes: 16 additions & 0 deletions libs/blog/data-access/src/lib/article.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,20 @@ export class ArticleService {
}),
);
}

getOne(slug: string): Observable<Article> {
return this.contentfulService
.getEntries(this.contentType, { 'fields.slug[match]': slug })
.pipe(
map(
(entries: EntryCollection<EntrySkeletonType, undefined, string>) => {
if (entries.items.length === 0) {
throw new Error('Article not found');
}

return Article.fromEntry(entries.items[0], entries.includes?.Asset);
},
),
);
}
}
4 changes: 3 additions & 1 deletion libs/blog/data-access/src/lib/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ export class Article {
createdAt = '';
updatedAt = '';
tags: Tag[] = [];
content: any;

static fromEntry(
entry: Entry<EntrySkeletonType, undefined, string>,
assets?: ContentfulAsset[]
assets?: ContentfulAsset[],
): Article {
const article = new Article();
article.title = entry.fields['title'] as string;
Expand All @@ -45,6 +46,7 @@ export class Article {
.join(),
});
});
article.content = entry.fields['content'];
return article;
}
}
36 changes: 36 additions & 0 deletions libs/blog/feature-article/.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": "blog",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "blog",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}
7 changes: 7 additions & 0 deletions libs/blog/feature-article/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# blog-article

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

## Running unit tests

Run `nx test blog-article` to execute the unit tests.
22 changes: 22 additions & 0 deletions libs/blog/feature-article/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'blog-article',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/blog/feature-article',
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/blog/feature-article/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "blog-article",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/blog/feature-article/src",
"prefix": "blog",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/blog/feature-article/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}
1 change: 1 addition & 0 deletions libs/blog/feature-article/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/blog-article.component';
23 changes: 23 additions & 0 deletions libs/blog/feature-article/src/lib/blog-article.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@if (article$ | async; as article) {
<article class="relative my-10 max-w-3xl pt-10">
<dl>
<dt class="sr-only">Date</dt>
<dd class="top-0 whitespace-nowrap text-sm leading-6">
<time [attr.datetime]="article.createdAt">{{
article.createdAt | date: 'mediumDate'
}}</time>
</dd>
</dl>
<h1
class="my-5 text-2xl font-bold leading-[3rem] tracking-tight text-black lg:text-4xl dark:text-white"
>
{{ article.title }}
</h1>

<h2>
{{ article.abstract }}
</h2>

<div class="my-10"></div>
</article>
}
21 changes: 21 additions & 0 deletions libs/blog/feature-article/src/lib/blog-article.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BlogArticleComponent } from './blog-article.component';

describe('BlogArticleComponent', () => {
let component: BlogArticleComponent;
let fixture: ComponentFixture<BlogArticleComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [BlogArticleComponent],
}).compileComponents();

fixture = TestBed.createComponent(BlogArticleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
44 changes: 44 additions & 0 deletions libs/blog/feature-article/src/lib/blog-article.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Params } from '@angular/router';

import { map, Observable } from 'rxjs';

import { Article, ArticleService } from '@valerymelou/blog/data-access';
import { MetadataService } from '@valerymelou/shared/seo';

@Component({
selector: 'blog-article',
standalone: true,
imports: [CommonModule],
templateUrl: './blog-article.component.html',
})
export class BlogArticleComponent {
article$!: Observable<Article>;

constructor(
private route: ActivatedRoute,
private articleService: ArticleService,
private metadataService: MetadataService,
) {
this.route.params.subscribe({
next: (params: Params) => {
if (params['slug']) this.getArticle(params['slug']);
},
});
}

getArticle(slug: string): void {
slug = slug.split('-').slice(3).join('-');
this.article$ = this.articleService.getOne(slug).pipe(
map((article: Article) => {
this.metadataService.updateMetadata({
title: article.title,
description: article.abstract,
image: article.cover?.url,
});
return article;
}),
);
}
}
8 changes: 8 additions & 0 deletions libs/blog/feature-article/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/blog/feature-article/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
}
}
17 changes: 17 additions & 0 deletions libs/blog/feature-article/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
"jest.config.ts",
"src/**/*.test.ts"
],
"include": ["src/**/*.ts"]
}
16 changes: 16 additions & 0 deletions libs/blog/feature-article/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"target": "es2016",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
4 changes: 2 additions & 2 deletions libs/blog/feature-home/src/lib/blog-home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {
Article,
ArticleService,
Results,
} from '@valerymelou/blob/data-access';
} from '@valerymelou/blog/data-access';
import { LinkComponent } from '@valerymelou/shared/ui';
import { RouterModule } from '@angular/router';
import { MetadataService } from '@valerymelou/shared/seo';

@Component({
selector: 'blog-blog-home',
selector: 'blog-home',
standalone: true,
imports: [CommonModule, RouterModule, LinkComponent],
templateUrl: './blog-home.component.html',
Expand Down
2 changes: 1 addition & 1 deletion libs/shared/layout/src/lib/navbar/navbar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class NavbarComponent {
if (event instanceof NavigationEnd) {
this.isAbout = event.urlAfterRedirects === '/about';
this.isProject = event.urlAfterRedirects === '/projects';
this.isBlog = event.urlAfterRedirects === '/blog';
this.isBlog = event.urlAfterRedirects.startsWith('/blog');
}
});
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@angular/platform-browser": "18.0.0",
"@angular/platform-browser-dynamic": "18.0.0",
"@angular/router": "18.0.0",
"@contentful/rich-text-types": "^16.6.1",
"@ng-icons/bootstrap-icons": "^27.3.1",
"@ng-icons/core": "^27.3.1",
"@ng-icons/material-icons": "^27.5.2",
Expand Down
7 changes: 4 additions & 3 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@valerymelou/blob/data-access": ["libs/blog/data-access/src/index.ts"],
"@valerymelou/blog/article": ["libs/blog/feature-article/src/index.ts"],
"@valerymelou/blog/data-access": ["libs/blog/data-access/src/index.ts"],
"@valerymelou/blog/home": ["libs/blog/feature-home/src/index.ts"],
"@valerymelou/cms/contentful": ["libs/cms/contentful/src/index.ts"],
"@valerymelou/pages/about": ["libs/pages/about/src/index.ts"],
"@valerymelou/pages/home": ["libs/pages/home/src/index.ts"],
"@valerymelou/pages/projects": ["libs/pages/projects/src/index.ts"],
"@valerymelou/shared/layout": ["libs/shared/layout/src/index.ts"],
"@valerymelou/shared/ui": ["libs/shared/ui/src/index.ts"],
"@valerymelou/shared/seo": ["libs/shared/seo/src/index.ts"],
"@valerymelou/shared/theming": ["libs/shared/theming/src/index.ts"]
"@valerymelou/shared/theming": ["libs/shared/theming/src/index.ts"],
"@valerymelou/shared/ui": ["libs/shared/ui/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
Expand Down
6 changes: 6 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,11 @@
resolved "https://registry.yarnpkg.com/@contentful/rich-text-types/-/rich-text-types-16.5.0.tgz#f82f38d2131ebae10458990cbb7574d9b77b77e7"
integrity sha512-Z5fls/6Hs+ETq6vx+sfiq+n+z3dwurPVJukgLvThdvzs4VlChoPkRIKr4WrBdjzLYqUspawlf7XvtuUpp32glw==

"@contentful/rich-text-types@^16.6.1":
version "16.6.1"
resolved "https://registry.yarnpkg.com/@contentful/rich-text-types/-/rich-text-types-16.6.1.tgz#d7a0890b885ab681028aa7f1085e219d8b2c51be"
integrity sha512-Rb8QusSMSTMUP4dbpBDWgwGJoqKDYRxPcNXySP0URYTQ6pViJ2SXhVc81RJCgJ5Fb02n5ctbvDYGtcnBH8yhDw==

"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
Expand Down Expand Up @@ -10382,6 +10387,7 @@ wildcard@^2.0.0:
integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
name wrap-ansi-cjs
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand Down

0 comments on commit cb28067

Please sign in to comment.