From a238014250ba75a90b2ea2d73cf65327e03237dc Mon Sep 17 00:00:00 2001 From: RobertSenkel Date: Thu, 13 Feb 2025 14:42:14 +0100 Subject: [PATCH 1/4] added sorting for location pages and changed default count items for each page --- src/app/core/services/posts.service.ts | 2 +- src/app/features/location/location.component.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/core/services/posts.service.ts b/src/app/core/services/posts.service.ts index a4d3f8f3..8d4e1151 100644 --- a/src/app/core/services/posts.service.ts +++ b/src/app/core/services/posts.service.ts @@ -12,7 +12,7 @@ import { SPECIAL_CATEGORIES } from '../config/configuration-tokens'; import { AuthorsList } from '../model/author.model'; import { Location } from '../model/locations.model'; -const POSTS_PER_PAGE = 8; +const POSTS_PER_PAGE = 12; @Injectable({ providedIn: 'root', diff --git a/src/app/features/location/location.component.ts b/src/app/features/location/location.component.ts index 179271aa..199c1cea 100644 --- a/src/app/features/location/location.component.ts +++ b/src/app/features/location/location.component.ts @@ -51,7 +51,8 @@ export class LocationComponent { page, undefined, false, - (post: Post) => post.location === location + (post: Post) => post.location === location, + (a, b) => this.compareByDate(a, b) ) ) ); @@ -68,4 +69,11 @@ export class LocationComponent { navigate(page: PageEvent) { this.navigationService.navigate(page.pageIndex); } + + private compareByDate(a: Post, b: Post): number { + return ( + new Date(b.date?.trim() ?? 0).getTime() - + new Date(a.date?.trim() ?? 0).getTime() + ); + } } From 397f3ebbdecaee9e2e65dbd15addb97a8edb4245 Mon Sep 17 00:00:00 2001 From: RobertSenkel Date: Mon, 17 Feb 2025 12:40:49 +0100 Subject: [PATCH 2/4] prettier --- src/app/app.component.ts | 6 +- src/app/app.config.ts | 27 +++-- src/app/app.meta.config.ts | 18 ++-- src/app/app.routes.map.ts | 10 +- src/app/app.routes.ts | 62 +++++++---- .../components/author/author.component.html | 5 +- src/app/components/author/author.component.ts | 1 - .../components/avatar/avatar.component.html | 100 ++++++++++++------ .../components/avatar/avatar.component.scss | 21 ++-- .../avatar/avatar.component.spec.ts | 7 +- src/app/components/avatar/avatar.component.ts | 12 ++- .../engineering-content.component.html | 5 +- .../engineering-content.component.ts | 3 +- .../meetup-footer/meetup-footer.component.ts | 3 +- .../post-featured.component.html | 3 +- .../post-featured.component.scss | 2 +- .../post-image/post-image.component.html | 7 +- .../post-item/post-item.component.html | 3 +- .../transition/transition.component.html | 2 +- .../categories-tab.component.ts | 4 +- .../dark-mode-toggle.component.html | 3 +- .../dark-mode-toggle.component.scss | 1 - .../dark-mode-toggle.component.ts | 19 +++- .../locations-tab/locations-tab.component.ts | 5 +- .../layout/navigation/is-active.directive.ts | 4 +- src/app/core/model/author.model.ts | 2 +- src/app/core/model/categories.model.ts | 5 +- src/app/core/model/content.model.ts | 2 +- src/app/core/model/observability.model.ts | 12 +-- src/app/core/model/post.model.ts | 2 +- src/app/core/services/assets.service.ts | 6 +- src/app/core/services/authors.service.ts | 15 +-- .../core/services/html-in-markdown.service.ts | 15 +-- .../core/services/observability.service.ts | 44 ++++---- src/app/core/services/posts.service.ts | 57 ++++++---- src/app/core/utils/headers.pipe.ts | 15 ++- src/app/core/utils/post-url.pipe.ts | 7 +- src/app/core/utils/route-events.ts | 47 ++++---- src/app/features/author/author.component.html | 20 ++-- src/app/features/author/author.component.scss | 2 +- src/app/features/author/author.component.ts | 8 +- .../features/authors/authors.component.html | 3 +- .../authors/authors.component.spec.ts | 7 +- .../features/category/category.component.html | 12 +-- .../features/location/location.component.html | 16 +-- .../features/location/location.component.ts | 50 ++++++++- src/app/features/meetups/meetups.component.ts | 30 +++--- .../not-found/not-found.component.html | 9 +- .../not-found/not-found.component.scss | 1 - src/app/features/post/post.component.html | 2 +- src/app/features/post/post.component.scss | 5 +- src/app/features/post/post.component.ts | 9 +- .../related-posts.component.spec.ts | 7 +- .../related-posts/related-posts.component.ts | 30 +++--- src/app/features/search/search.component.ts | 24 +++-- src/app/markdown.config.ts | 4 +- src/index.html | 5 +- styles/_contrast.scss | 26 +++-- styles/_typography.scss | 13 +-- styles/styles.scss | 12 +-- tools/build-posts/index.js | 11 +- .../src/meetup-scaffolder/index_spec.ts | 6 +- .../src/post-scaffolder/index.ts | 6 +- tools/process-images/index.js | 43 ++++---- tools/publish/index.js | 13 +-- 65 files changed, 565 insertions(+), 371 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d96e5463..fc096233 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -6,11 +6,7 @@ import { FooterComponent } from './core/layout/footer/footer.component'; @Component({ selector: 'blog-root', standalone: true, - imports: [ - NavigationComponent, - FooterComponent, - RouterOutlet - ], + imports: [NavigationComponent, FooterComponent, RouterOutlet], templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 88e3aba3..38665659 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -10,7 +10,7 @@ import { provideRouter, withComponentInputBinding, withInMemoryScrolling, - withRouterConfig + withRouterConfig, } from '@angular/router'; import { routes } from './app.routes'; @@ -24,7 +24,7 @@ import { AUTHORS_AVATAR_PATH_TOKEN, USE_PROCESSED_IMAGES, O11Y_CONFIG_TOKEN, - SPECIAL_CATEGORIES + SPECIAL_CATEGORIES, } from './core/config/configuration-tokens'; import { HtmlInMarkdownService } from './core/services/html-in-markdown.service'; import { ObservabilityConfig } from './core/model/observability.model'; @@ -68,8 +68,17 @@ export const appConfig: ApplicationConfig = { { provide: APP_INITIALIZER, multi: true, - useFactory: (...deps: any) => () => markdownConfig.apply(this, deps), - deps: [MarkdownService, DOCUMENT, HtmlInMarkdownService, AssetsService, Router], + useFactory: + (...deps: any) => + () => + markdownConfig.apply(this, deps), + deps: [ + MarkdownService, + DOCUMENT, + HtmlInMarkdownService, + AssetsService, + Router, + ], }, { provide: APP_INITIALIZER, @@ -84,12 +93,10 @@ export const appConfig: ApplicationConfig = { { provide: USE_PROCESSED_IMAGES, useValue: !isDevMode(), - },{ + }, + { provide: SPECIAL_CATEGORIES, - useValue: [ - 'principles', - 'meetups', - ] - } + useValue: ['principles', 'meetups'], + }, ], }; diff --git a/src/app/app.meta.config.ts b/src/app/app.meta.config.ts index b2760eed..682f9d61 100644 --- a/src/app/app.meta.config.ts +++ b/src/app/app.meta.config.ts @@ -1,9 +1,10 @@ -import { MetaDefinition } from "@angular/platform-browser"; -import { PostContent } from "./core/model/post.model"; +import { MetaDefinition } from '@angular/platform-browser'; +import { PostContent } from './core/model/post.model'; const prodUrl = 'https://engineering.backbase.com/'; const rootTitle = 'Backbase Engineering'; -const rootDescription = 'Backbase is a global fintech company creating the best digital banking solutions on the planet. We are a young-spirited, diverse (45+ nationalities), fast-growing and leading company in our niche.'; +const rootDescription = + 'Backbase is a global fintech company creating the best digital banking solutions on the planet. We are a young-spirited, diverse (45+ nationalities), fast-growing and leading company in our niche.'; export const defaultMeta: MetaDefinition[] = [ { @@ -36,10 +37,13 @@ export const notFoundMeta: MetaDefinition[] = [ { name: 'robots', content: 'noindex, nofollow', - } -] + }, +]; -export const getPostMeta = ({ excerpt, title, displayTeaser }: PostContent, url: string) => [ +export const getPostMeta = ( + { excerpt, title, displayTeaser }: PostContent, + url: string +) => [ { name: 'description', content: excerpt, @@ -60,4 +64,4 @@ export const getPostMeta = ({ excerpt, title, displayTeaser }: PostContent, url: property: 'og:description', content: excerpt, }, -]; \ No newline at end of file +]; diff --git a/src/app/app.routes.map.ts b/src/app/app.routes.map.ts index 0ac4859f..a4d1ff3a 100644 --- a/src/app/app.routes.map.ts +++ b/src/app/app.routes.map.ts @@ -1,8 +1,14 @@ import { Route } from '@angular/router'; const routes = [ - ['2023/12/13/angular-micro-frontends', '2023/12/13/micro-frontends-with-module-federation'], - ['2023/12/13/setup-microfrontend', '2023/12/13/micro-frontends-with-module-federation'], + [ + '2023/12/13/angular-micro-frontends', + '2023/12/13/micro-frontends-with-module-federation', + ], + [ + '2023/12/13/setup-microfrontend', + '2023/12/13/micro-frontends-with-module-federation', + ], ['2023/10/06/code-coverage', '2023/10/06/code-coverage-for-unit-tests'], [ '2023/09/30/installing-hms-core-in-as-emulator', diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index a50cfae7..5e068a2d 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,4 +1,10 @@ -import { ActivatedRouteSnapshot, Route, Router, RouterStateSnapshot, Routes } from '@angular/router'; +import { + ActivatedRouteSnapshot, + Route, + Router, + RouterStateSnapshot, + Routes, +} from '@angular/router'; import { postRedirects } from './app.routes.map'; import { EMPTY, Subject, catchError, map, tap } from 'rxjs'; import { inject, makeStateKey, TransferState } from '@angular/core'; @@ -18,12 +24,16 @@ export const routes: Routes = [ { path: '', loadComponent: () => - import('./features/home/home.component').then(mod => mod.HomeComponent), + import('./features/home/home.component').then( + mod => mod.HomeComponent + ), }, { path: 'people/:author', loadComponent: () => - import('./features/author/author.component').then(m => m.AuthorComponent), + import('./features/author/author.component').then( + m => m.AuthorComponent + ), }, { path: ':year/:month/:day/:permalink', @@ -92,8 +102,8 @@ export const routes: Routes = [ ), data: { meta: notFoundMeta }, }, - ] - } + ], + }, ]; function getRouteData(): Partial { @@ -106,41 +116,57 @@ function getRouteData(): Partial { const transferState = inject(TransferState); const stateKey = makeStateKey(url); if (transferState.hasKey(stateKey)) { - const post = transferState.get(stateKey, undefined); + const post = transferState.get( + stateKey, + undefined + ); postData.next(post as PostContent); return post; } - return inject(PostsService).getPost(url) + return inject(PostsService) + .getPost(url) .pipe( tap(post => { transferState.set(stateKey, post); - postData.next(post)} - ), + postData.next(post); + }), catchError((_: any) => { router.navigate(['**'], { skipLocationChange: true }); return EMPTY; }) - ) + ); }, meta: (activatedRoute: ActivatedRouteSnapshot) => { const url = activatedRoute.url.map(({ path }) => path).join('/'); const transferState = inject(TransferState); const stateKey = makeStateKey(url); if (transferState.hasKey(stateKey)) { - return getPostMeta(transferState.get(stateKey, undefined) as PostContent, url); + return getPostMeta( + transferState.get( + stateKey, + undefined + ) as PostContent, + url + ); } - return postData.pipe(map((post) => getPostMeta(post, url))); - } + return postData.pipe(map(post => getPostMeta(post, url))); + }, }, title: (activatedRoute: ActivatedRouteSnapshot) => { const url = activatedRoute.url.map(({ path }) => path).join('/'); const transferState = inject(TransferState); const stateKey = makeStateKey(url); - const getTitle = ({ title }: PostContent) => `${title} | ${activatedRoute.parent?.title}`; + const getTitle = ({ title }: PostContent) => + `${title} | ${activatedRoute.parent?.title}`; if (transferState.hasKey(stateKey)) { - return getTitle(transferState.get(stateKey, undefined) as PostContent); + return getTitle( + transferState.get( + stateKey, + undefined + ) as PostContent + ); } - return postData.pipe(map((post) => getTitle(post))); - } - } + return postData.pipe(map(post => getTitle(post))); + }, + }; } diff --git a/src/app/components/author/author.component.html b/src/app/components/author/author.component.html index 997d3ebb..c71879a3 100644 --- a/src/app/components/author/author.component.html +++ b/src/app/components/author/author.component.html @@ -5,13 +5,12 @@ sm: size === 'sm', md: size === 'md', lg: size === 'lg', - muted: muted + muted: muted, }"> + format="circle">
{{ author.fullname }}
@if (size === 'lg') { diff --git a/src/app/components/author/author.component.ts b/src/app/components/author/author.component.ts index 643d3190..12419749 100644 --- a/src/app/components/author/author.component.ts +++ b/src/app/components/author/author.component.ts @@ -29,5 +29,4 @@ export class AuthorComponent { @Input() muted = false; author!: Author; - } diff --git a/src/app/components/avatar/avatar.component.html b/src/app/components/avatar/avatar.component.html index d78edcbd..73c7163d 100644 --- a/src/app/components/avatar/avatar.component.html +++ b/src/app/components/avatar/avatar.component.html @@ -1,40 +1,70 @@ -
+
@if (placeholder) { - + - - - - - - - - - - - + + + + + + + + + + + } @else { diff --git a/src/app/components/avatar/avatar.component.scss b/src/app/components/avatar/avatar.component.scss index 219cd8bf..fa18adc0 100644 --- a/src/app/components/avatar/avatar.component.scss +++ b/src/app/components/avatar/avatar.component.scss @@ -4,9 +4,11 @@ display: inline-flex; width: 100%; height: 100%; - + &__circle { - img, svg, &::before { + img, + svg, + &::before { border-radius: 100rem; } } @@ -15,11 +17,11 @@ &::before { content: ''; background: repeating-linear-gradient( - 55deg, - var(--blog-palette-accent), - var(--blog-palette-accent) 5px, - transparent 5px, - transparent 10px + 55deg, + var(--blog-palette-accent), + var(--blog-palette-accent) 5px, + transparent 5px, + transparent 10px ); position: absolute; height: 100%; @@ -30,7 +32,8 @@ } } - img, svg { + img, + svg { position: relative; background: var(--blog-palette-neutral); width: 100%; @@ -38,6 +41,6 @@ } svg > * { - opacity: .5; + opacity: 0.5; } } diff --git a/src/app/components/avatar/avatar.component.spec.ts b/src/app/components/avatar/avatar.component.spec.ts index 2e480304..eb978790 100644 --- a/src/app/components/avatar/avatar.component.spec.ts +++ b/src/app/components/avatar/avatar.component.spec.ts @@ -8,10 +8,9 @@ describe('AvatarComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AvatarComponent] - }) - .compileComponents(); - + imports: [AvatarComponent], + }).compileComponents(); + fixture = TestBed.createComponent(AvatarComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/src/app/components/avatar/avatar.component.ts b/src/app/components/avatar/avatar.component.ts index 42816da2..7244a221 100644 --- a/src/app/components/avatar/avatar.component.ts +++ b/src/app/components/avatar/avatar.component.ts @@ -1,4 +1,10 @@ -import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + Input, + ViewChild, +} from '@angular/core'; import { NgClass } from '@angular/common'; @Component({ @@ -9,7 +15,6 @@ import { NgClass } from '@angular/common'; styleUrl: './avatar.component.scss', }) export class AvatarComponent implements AfterViewInit { - @Input() url!: string | undefined; @Input() format: 'circle' | 'square' = 'circle'; @Input() shadow = false; @@ -21,8 +26,7 @@ export class AvatarComponent implements AfterViewInit { ngAfterViewInit(): void { this.image?.nativeElement?.addEventListener('error', () => { - this.placeholder = true + this.placeholder = true; }); } - } diff --git a/src/app/components/engineering-content/engineering-content.component.html b/src/app/components/engineering-content/engineering-content.component.html index a31a13ba..5f0b204f 100644 --- a/src/app/components/engineering-content/engineering-content.component.html +++ b/src/app/components/engineering-content/engineering-content.component.html @@ -12,10 +12,7 @@

Explore Our Engineering Approach: Check these articles!

Software Engineering

- +
diff --git a/src/app/components/engineering-content/engineering-content.component.ts b/src/app/components/engineering-content/engineering-content.component.ts index 2beaa466..4bcc29b9 100644 --- a/src/app/components/engineering-content/engineering-content.component.ts +++ b/src/app/components/engineering-content/engineering-content.component.ts @@ -11,5 +11,4 @@ import { RouterLink } from '@angular/router'; templateUrl: './engineering-content.component.html', styleUrl: './engineering-content.component.scss', }) -export class EngineeringContentComponent { -} +export class EngineeringContentComponent {} diff --git a/src/app/components/meetup-footer/meetup-footer.component.ts b/src/app/components/meetup-footer/meetup-footer.component.ts index de87daca..bfa3225e 100644 --- a/src/app/components/meetup-footer/meetup-footer.component.ts +++ b/src/app/components/meetup-footer/meetup-footer.component.ts @@ -9,5 +9,4 @@ import { MatRippleModule } from '@angular/material/core'; templateUrl: './meetup-footer.component.html', styleUrl: './meetup-footer.component.scss', }) -export class MeetupFooterComponent { -} +export class MeetupFooterComponent {} diff --git a/src/app/components/post-featured/post-featured.component.html b/src/app/components/post-featured/post-featured.component.html index 222c5952..d2fd3a0e 100644 --- a/src/app/components/post-featured/post-featured.component.html +++ b/src/app/components/post-featured/post-featured.component.html @@ -5,8 +5,7 @@ diff --git a/src/app/core/layout/categories-tab/categories-tab.component.ts b/src/app/core/layout/categories-tab/categories-tab.component.ts index 4777ccc4..64557051 100644 --- a/src/app/core/layout/categories-tab/categories-tab.component.ts +++ b/src/app/core/layout/categories-tab/categories-tab.component.ts @@ -18,7 +18,9 @@ export class CategoriesTabComponent { .getCategories() .pipe( map(categories => - categories.filter(category => !['principles', 'meetups'].includes(category)) + categories.filter( + category => !['principles', 'meetups'].includes(category) + ) ) ); selectedCategory$ = this.activatedRoute.paramMap.pipe( diff --git a/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.html b/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.html index ead2be20..ca8c931e 100644 --- a/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.html +++ b/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.html @@ -3,8 +3,7 @@ + (change)="onChange($event)">
} diff --git a/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.scss b/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.scss index 898feacc..a1fd4168 100644 --- a/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.scss +++ b/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.scss @@ -17,7 +17,6 @@ } .mdc-switch--selected { - .mdc-switch__handle-track { transform: translateX(calc(100% - 2px)); } diff --git a/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.ts b/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.ts index f6df7f21..01194225 100644 --- a/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.ts +++ b/src/app/core/layout/dark-mode-toggle/dark-mode-toggle.component.ts @@ -1,4 +1,12 @@ -import { AfterViewInit, ChangeDetectionStrategy, Component, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + TemplateRef, + ViewChild, + ViewContainerRef, + ViewEncapsulation, +} from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatSlideToggleChange, @@ -15,10 +23,11 @@ import { map } from 'rxjs'; templateUrl: './dark-mode-toggle.component.html', styleUrl: './dark-mode-toggle.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, }) export class DarkModeToggleComponent implements AfterViewInit { - @ViewChild('darkModeSwitch', { read: ViewContainerRef }) toggler!: ViewContainerRef; + @ViewChild('darkModeSwitch', { read: ViewContainerRef }) + toggler!: ViewContainerRef; @ViewChild('customIcons', { static: true }) darkIcon!: TemplateRef; dark$ = this.themeModeService.dark$.pipe(map(enabled => ({ enabled }))); @@ -26,7 +35,9 @@ export class DarkModeToggleComponent implements AfterViewInit { constructor(private themeModeService: ThemeModeService) {} ngAfterViewInit(): void { - const original = this.toggler.element.nativeElement.querySelector('.mdc-switch__icons') as HTMLElement; + const original = this.toggler.element.nativeElement.querySelector( + '.mdc-switch__icons' + ) as HTMLElement; const customIcons = this.toggler.createEmbeddedView(this.darkIcon); original.replaceChildren(...customIcons.rootNodes); } diff --git a/src/app/core/layout/locations-tab/locations-tab.component.ts b/src/app/core/layout/locations-tab/locations-tab.component.ts index b48395cc..e1eeb394 100644 --- a/src/app/core/layout/locations-tab/locations-tab.component.ts +++ b/src/app/core/layout/locations-tab/locations-tab.component.ts @@ -4,7 +4,7 @@ import { AsyncPipe } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { PostsService } from '../../services/posts.service'; import { Location } from '../../model/locations.model'; -import { LocationsComponent } from "../../../components/locations/locations.component"; +import { LocationsComponent } from '../../../components/locations/locations.component'; @Component({ selector: 'blog-locations-tab', @@ -14,8 +14,7 @@ import { LocationsComponent } from "../../../components/locations/locations.comp styleUrl: './locations-tab.component.scss', }) export class LocationsTabComponent { - locations$: Observable = this.postsService - .getLocations(); + locations$: Observable = this.postsService.getLocations(); selectedLocation$ = this.activatedRoute.paramMap.pipe( map(params => params.get('loc') as Location) diff --git a/src/app/core/layout/navigation/is-active.directive.ts b/src/app/core/layout/navigation/is-active.directive.ts index 2e744eb4..08922f6d 100644 --- a/src/app/core/layout/navigation/is-active.directive.ts +++ b/src/app/core/layout/navigation/is-active.directive.ts @@ -35,7 +35,9 @@ export class IsActiveDirective implements OnInit { map((event: any) => event.url.split('?')[0]) ) .subscribe((url: string) => { - this.toggleClass(url.replace(/^\//, this.locationStrategy.getBaseHref())); + this.toggleClass( + url.replace(/^\//, this.locationStrategy.getBaseHref()) + ); }); } diff --git a/src/app/core/model/author.model.ts b/src/app/core/model/author.model.ts index 64de531f..939f6b1d 100644 --- a/src/app/core/model/author.model.ts +++ b/src/app/core/model/author.model.ts @@ -1,4 +1,4 @@ -import { ProcessedAsset } from "./content.model"; +import { ProcessedAsset } from './content.model'; export interface Author { avatar: string; diff --git a/src/app/core/model/categories.model.ts b/src/app/core/model/categories.model.ts index 3a8e1379..e95de688 100644 --- a/src/app/core/model/categories.model.ts +++ b/src/app/core/model/categories.model.ts @@ -11,7 +11,4 @@ export enum Category { 'mobile' = 'Mobile', } -export const SpecialCategories = [ - 'principles', - 'meetups' -]; +export const SpecialCategories = ['principles', 'meetups']; diff --git a/src/app/core/model/content.model.ts b/src/app/core/model/content.model.ts index 01e52b18..0605e9a0 100644 --- a/src/app/core/model/content.model.ts +++ b/src/app/core/model/content.model.ts @@ -20,4 +20,4 @@ export interface Header { export type ImageSize = 'sm' | 'md' | 'lg'; -export type ProcessedAsset = { [size in ImageSize]: string } | undefined; \ No newline at end of file +export type ProcessedAsset = { [size in ImageSize]: string } | undefined; diff --git a/src/app/core/model/observability.model.ts b/src/app/core/model/observability.model.ts index aae37729..4591ca9f 100644 --- a/src/app/core/model/observability.model.ts +++ b/src/app/core/model/observability.model.ts @@ -1,7 +1,7 @@ export interface ObservabilityConfig { - apiKey: string, - appName: string, - version: string, - url: string, - enabled: boolean, -} \ No newline at end of file + apiKey: string; + appName: string; + version: string; + url: string; + enabled: boolean; +} diff --git a/src/app/core/model/post.model.ts b/src/app/core/model/post.model.ts index fdabe6d4..4da6f881 100644 --- a/src/app/core/model/post.model.ts +++ b/src/app/core/model/post.model.ts @@ -20,7 +20,7 @@ export interface Post { export type PostContent = Post & { markdown: string; -} +}; export interface Posts { posts: Post[]; diff --git a/src/app/core/services/assets.service.ts b/src/app/core/services/assets.service.ts index 413608d0..8c10fadc 100644 --- a/src/app/core/services/assets.service.ts +++ b/src/app/core/services/assets.service.ts @@ -3,10 +3,12 @@ import { USE_PROCESSED_IMAGES } from '../config/configuration-tokens'; import { ImageSize } from '../model/content.model'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class AssetsService { - constructor(@Inject(USE_PROCESSED_IMAGES) private useProcessedImages: boolean) { } + constructor( + @Inject(USE_PROCESSED_IMAGES) private useProcessedImages: boolean + ) {} getAssetPath(url: string, size: ImageSize) { if (this.useProcessedImages && !url.startsWith('http')) { diff --git a/src/app/core/services/authors.service.ts b/src/app/core/services/authors.service.ts index 20147644..d6ebd2ff 100644 --- a/src/app/core/services/authors.service.ts +++ b/src/app/core/services/authors.service.ts @@ -22,7 +22,7 @@ export class AuthorsService { ...authors[curr], fullname: curr, url: `/people/${getAuthorPermalink(curr)}`, - displayAvatar: this.generateAvatarPaths(authors[curr]?.avatar) + displayAvatar: this.generateAvatarPaths(authors[curr]?.avatar), }, }), {} @@ -34,7 +34,7 @@ export class AuthorsService { constructor( private httpClient: HttpClient, private assetsService: AssetsService, - @Inject(AUTHORS_AVATAR_PATH_TOKEN) private basePath: string, + @Inject(AUTHORS_AVATAR_PATH_TOKEN) private basePath: string ) {} getAuthors(): Observable { @@ -56,10 +56,13 @@ export class AuthorsService { private generateAvatarPaths(url?: string) { const sizes: ImageSize[] = ['sm', 'lg']; if (url) { - return sizes.reduce((acc, curr) => ({ - ...acc, - [curr]: `authors/${this.assetsService.getAssetPath(url, curr)}` - }), {}); + return sizes.reduce( + (acc, curr) => ({ + ...acc, + [curr]: `authors/${this.assetsService.getAssetPath(url, curr)}`, + }), + {} + ); } return undefined; } diff --git a/src/app/core/services/html-in-markdown.service.ts b/src/app/core/services/html-in-markdown.service.ts index 997c4eac..1b21e21b 100644 --- a/src/app/core/services/html-in-markdown.service.ts +++ b/src/app/core/services/html-in-markdown.service.ts @@ -2,24 +2,25 @@ import { DOCUMENT } from '@angular/common'; import { Inject, Injectable } from '@angular/core'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class HtmlInMarkdownService { - - constructor(@Inject(DOCUMENT) private document: Document) { } + constructor(@Inject(DOCUMENT) private document: Document) {} add(html: string) { if (html.startsWith('` + return ``; } parseAll() { const frames = this.document.querySelectorAll('iframe[data-content]'); frames.forEach(iframe => { - const content = this.document.defaultView?.window.atob?.(iframe.getAttribute('data-content') ?? ''); + const content = this.document.defaultView?.window.atob?.( + iframe.getAttribute('data-content') ?? '' + ); iframe.removeAttribute('data-content'); const frameDoc = (iframe as any).contentWindow.document as Document; frameDoc.open(); diff --git a/src/app/core/services/observability.service.ts b/src/app/core/services/observability.service.ts index 82ea8165..c83a400c 100644 --- a/src/app/core/services/observability.service.ts +++ b/src/app/core/services/observability.service.ts @@ -1,13 +1,16 @@ import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { - WebTracerProvider, - BatchSpanProcessor, - TraceIdRatioBasedSampler, + WebTracerProvider, + BatchSpanProcessor, + TraceIdRatioBasedSampler, } from '@opentelemetry/sdk-trace-web'; import { ZoneContextManager } from '@opentelemetry/context-zone-peer-dep'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { Resource } from '@opentelemetry/resources'; -import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'; +import { + SEMRESATTRS_SERVICE_NAME, + SEMRESATTRS_SERVICE_VERSION, +} from '@opentelemetry/semantic-conventions'; import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'; import opentelemetry, { Attributes, Span } from '@opentelemetry/api'; @@ -21,17 +24,16 @@ import { AttributeNames } from '@opentelemetry/instrumentation-user-interaction' const ANONYMOUS_USER_ID = 'uid'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class ObservabilityService { - constructor( @Inject(O11Y_CONFIG_TOKEN) private config: ObservabilityConfig, @Inject(PLATFORM_ID) platformId: object, @Inject(DOCUMENT) private document: Document, private router: Router ) { - if(isPlatformBrowser(platformId) && config.enabled) { + if (isPlatformBrowser(platformId) && config.enabled) { this.initiateTracking(); } } @@ -44,14 +46,14 @@ export class ObservabilityService { [SEMRESATTRS_SERVICE_NAME]: appName, [SEMRESATTRS_SERVICE_VERSION]: version, sessionId: this.getSessionId(), - }), + }) ); const provider = new WebTracerProvider({ resource, sampler: new TraceIdRatioBasedSampler(1), }); - + provider.addSpanProcessor( new BatchSpanProcessor( new OTLPTraceExporter({ @@ -59,19 +61,19 @@ export class ObservabilityService { headers: { 'Bb-App-Key': apiKey, }, - }), - ), + }) + ) ); - + provider.register({ contextManager: new ZoneContextManager(), }); - + provider.getActiveSpanProcessor().onStart = (span: Span) => { span.setAttribute('view.name', document.title); span.setAttribute(AttributeNames.HTTP_URL, document.location.href); }; - + registerInstrumentations({ instrumentations: [ getWebAutoInstrumentations({ @@ -83,17 +85,21 @@ export class ObservabilityService { } public publishEvent(payload: Attributes, event: string) { - opentelemetry.trace.getTracer('@blog/observability').startActiveSpan(event, activeSpan => { - activeSpan.setAttributes(payload); - activeSpan.end(); - }); + opentelemetry.trace + .getTracer('@blog/observability') + .startActiveSpan(event, activeSpan => { + activeSpan.setAttributes(payload); + activeSpan.end(); + }); } private getSessionId(): string { const storage = this.document.defaultView?.window.sessionStorage; let sessionId: string = `${storage?.getItem(ANONYMOUS_USER_ID)}`; if (!this.isHex(sessionId)) { - const newSessionId = window?.crypto?.getRandomValues(new Uint32Array(1))[0].toString(16); + const newSessionId = window?.crypto + ?.getRandomValues(new Uint32Array(1))[0] + .toString(16); storage?.setItem(ANONYMOUS_USER_ID, newSessionId); sessionId = newSessionId; } diff --git a/src/app/core/services/posts.service.ts b/src/app/core/services/posts.service.ts index 8d4e1151..50227960 100644 --- a/src/app/core/services/posts.service.ts +++ b/src/app/core/services/posts.service.ts @@ -22,7 +22,9 @@ export class PostsService { .get('posts.json') .pipe( withLatestFrom(this.authorsService.getAuthors()), - map(([posts, authors]) => posts.map((post) => this.decoratePost(post, authors))), + map(([posts, authors]) => + posts.map(post => this.decoratePost(post, authors)) + ), shareReplay() ); @@ -31,7 +33,7 @@ export class PostsService { private authorsService: AuthorsService, private assetsService: AssetsService, private markdownService: MarkdownService, - @Inject(SPECIAL_CATEGORIES) private specialCategories: Category[], + @Inject(SPECIAL_CATEGORIES) private specialCategories: Category[] ) {} getAllPosts(): Observable { @@ -43,7 +45,9 @@ export class PostsService { map( posts => posts.find(({ featured }) => featured) ?? - posts.find(({ category }) => !this.specialCategories.includes(category)) + posts.find( + ({ category }) => !this.specialCategories.includes(category) + ) ) ); } @@ -53,7 +57,7 @@ export class PostsService { size: number = POSTS_PER_PAGE, filterFeatured: boolean = true, filterFn?: (post: Post) => boolean, - sortFn?: (a: Post, b: Post) => number, + sortFn?: (a: Post, b: Post) => number ): Observable { return this.getAllPosts().pipe( withLatestFrom(this.getHighlightedPost()), @@ -85,13 +89,18 @@ export class PostsService { } getPost(permalink: string | null): Observable { - return this.markdownService.getSource(`${permalink}/post.md`) - .pipe( - withLatestFrom(this.authorsService.getAuthors()), - map(([markdown, authors]) => ({ - ...this.decoratePost(extractPostMetaData(markdown) as Post, authors), - } as PostContent)), - ); + return this.markdownService.getSource(`${permalink}/post.md`).pipe( + withLatestFrom(this.authorsService.getAuthors()), + map( + ([markdown, authors]) => + ({ + ...this.decoratePost( + extractPostMetaData(markdown) as Post, + authors + ), + }) as PostContent + ) + ); } getCategories(): Observable { @@ -124,7 +133,7 @@ export class PostsService { }, {} ); - + const entries = Object.entries(locations); return entries .sort(([_, countA], [__, countB]) => countB - countA) @@ -133,7 +142,10 @@ export class PostsService { ); } - private decoratePost(post: Post | PostContent, authors: AuthorsList): Post | PostContent { + private decoratePost( + post: Post | PostContent, + authors: AuthorsList + ): Post | PostContent { return { ...post, specialCategory: this.specialCategories.includes(post.category), @@ -141,17 +153,22 @@ export class PostsService { displayTeaser: this.generateDisplayAssets(post.teaser), authors: post.authors.map(author => typeof author === 'string' ? authors[author] || author : author - ) - } + ), + }; } - private generateDisplayAssets(url?: string): { [size in ImageSize]: string } | undefined { + private generateDisplayAssets( + url?: string + ): { [size in ImageSize]: string } | undefined { const sizes: ImageSize[] = ['sm', 'md', 'lg']; if (url) { - return sizes.reduce((acc, curr) => ({ - ...acc, - [curr]: this.assetsService.getAssetPath(url, curr) - }), {} as { [size in ImageSize]: string }); + return sizes.reduce( + (acc, curr) => ({ + ...acc, + [curr]: this.assetsService.getAssetPath(url, curr), + }), + {} as { [size in ImageSize]: string } + ); } return undefined; } diff --git a/src/app/core/utils/headers.pipe.ts b/src/app/core/utils/headers.pipe.ts index 12733b50..8d7cc62e 100644 --- a/src/app/core/utils/headers.pipe.ts +++ b/src/app/core/utils/headers.pipe.ts @@ -5,10 +5,9 @@ import { HeaderNode } from '../model/content.model'; @Pipe({ name: 'headers', - standalone: true + standalone: true, }) export class HeadersPipe implements PipeTransform { - transform(markdown: string): HeaderNode[] { const regExp = new RegExp(//gm); const matches = this.markdownService @@ -43,14 +42,20 @@ export class HeadersPipe implements PipeTransform { return hierarchy; }, - [{ id: 'post-header', heading: title?.heading || 'Header', level: 1, children: [] }] + [ + { + id: 'post-header', + heading: title?.heading || 'Header', + level: 1, + children: [], + }, + ] ) .splice(0, 1); } constructor( private markdownService: MarkdownService, - @Inject(DOCUMENT) private document: Document, + @Inject(DOCUMENT) private document: Document ) {} - } diff --git a/src/app/core/utils/post-url.pipe.ts b/src/app/core/utils/post-url.pipe.ts index f16cded0..2035b5cc 100644 --- a/src/app/core/utils/post-url.pipe.ts +++ b/src/app/core/utils/post-url.pipe.ts @@ -8,7 +8,12 @@ import { getPermalink } from '@blog/utils'; }) export class PostUrlPipe implements PipeTransform { transform(post: Post, content?: string): string { - const postUrl = getPermalink(post.title, post.specialCategory, post.category, post.date); + const postUrl = getPermalink( + post.title, + post.specialCategory, + post.category, + post.date + ); if (content) { return `${postUrl}/${content}`; diff --git a/src/app/core/utils/route-events.ts b/src/app/core/utils/route-events.ts index dec2d025..06d2e113 100644 --- a/src/app/core/utils/route-events.ts +++ b/src/app/core/utils/route-events.ts @@ -1,21 +1,32 @@ -import { Meta, MetaDefinition } from "@angular/platform-browser"; -import { ObservabilityService } from "../services/observability.service"; -import { ActivationStart, EventType, Router } from "@angular/router"; -import { filter } from "rxjs"; +import { Meta, MetaDefinition } from '@angular/platform-browser'; +import { ObservabilityService } from '../services/observability.service'; +import { ActivationStart, EventType, Router } from '@angular/router'; +import { filter } from 'rxjs'; import { AttributeNames } from '@opentelemetry/instrumentation-user-interaction'; -export function routeEvents(router: Router, meta: Meta, o11y: ObservabilityService) { +export function routeEvents( + router: Router, + meta: Meta, + o11y: ObservabilityService +) { return () => { - router.events.pipe( - filter((event): event is ActivationStart => - event.type === EventType.ActivationEnd && !!event.snapshot.component), - ).subscribe(({ snapshot }: ActivationStart) => { - const tags: MetaDefinition[] = snapshot.data['meta'] ?? []; - meta.removeTag('name="robots"'); - tags.forEach((tag) => meta.updateTag(tag)); - o11y.publishEvent({ - [AttributeNames.EVENT_TYPE]: 'navigation' - }, 'page_view'); - }); - } -} \ No newline at end of file + router.events + .pipe( + filter( + (event): event is ActivationStart => + event.type === EventType.ActivationEnd && !!event.snapshot.component + ) + ) + .subscribe(({ snapshot }: ActivationStart) => { + const tags: MetaDefinition[] = snapshot.data['meta'] ?? []; + meta.removeTag('name="robots"'); + tags.forEach(tag => meta.updateTag(tag)); + o11y.publishEvent( + { + [AttributeNames.EVENT_TYPE]: 'navigation', + }, + 'page_view' + ); + }); + }; +} diff --git a/src/app/features/author/author.component.html b/src/app/features/author/author.component.html index ed587b44..31d8136b 100644 --- a/src/app/features/author/author.component.html +++ b/src/app/features/author/author.component.html @@ -7,16 +7,20 @@

{{ author.fullname }}

{{ author.role }}

- +
@@ -25,7 +29,9 @@

{{ author.role }}

} @if (notFound) { diff --git a/src/app/features/author/author.component.scss b/src/app/features/author/author.component.scss index e00a343c..c161a111 100644 --- a/src/app/features/author/author.component.scss +++ b/src/app/features/author/author.component.scss @@ -40,7 +40,7 @@ background-color: var(--blog-palette-neutral); mat-chip-row { - margin: 0 0.5rem + margin: 0 0.5rem; } } diff --git a/src/app/features/author/author.component.ts b/src/app/features/author/author.component.ts index 69e0a143..0ea114ce 100644 --- a/src/app/features/author/author.component.ts +++ b/src/app/features/author/author.component.ts @@ -42,7 +42,7 @@ export class AuthorComponent { param?.toLowerCase().replace(/\W/g, '') )?.[1] ), - tap((author) => { + tap(author => { if (!author) { this.notFound = true; } @@ -51,7 +51,11 @@ export class AuthorComponent { posts$: Observable = this.author$.pipe( switchMap(author => this.postsService.getPosts(undefined, undefined, false, (post: Post) => - post.authors.map((author) => typeof author === 'string' ? author : author.fullname).includes(author?.fullname ?? '') + post.authors + .map(author => + typeof author === 'string' ? author : author.fullname + ) + .includes(author?.fullname ?? '') ) ), map(({ posts }) => posts) diff --git a/src/app/features/authors/authors.component.html b/src/app/features/authors/authors.component.html index d03844c4..1483ef16 100644 --- a/src/app/features/authors/authors.component.html +++ b/src/app/features/authors/authors.component.html @@ -5,8 +5,7 @@ + class="authors__avatar">

{{ author.fullname }}

{{ author.role }}

diff --git a/src/app/features/authors/authors.component.spec.ts b/src/app/features/authors/authors.component.spec.ts index 2e35c015..9510a322 100644 --- a/src/app/features/authors/authors.component.spec.ts +++ b/src/app/features/authors/authors.component.spec.ts @@ -8,10 +8,9 @@ describe('AuthorsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AuthorsComponent] - }) - .compileComponents(); - + imports: [AuthorsComponent], + }).compileComponents(); + fixture = TestBed.createComponent(AuthorsComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/src/app/features/category/category.component.html b/src/app/features/category/category.component.html index 401e79ab..6a5912e8 100644 --- a/src/app/features/category/category.component.html +++ b/src/app/features/category/category.component.html @@ -6,7 +6,7 @@
- +
- - + + } @placeholder {
- +
} diff --git a/src/app/features/location/location.component.html b/src/app/features/location/location.component.html index c0c11371..0ed5a278 100644 --- a/src/app/features/location/location.component.html +++ b/src/app/features/location/location.component.html @@ -1,3 +1,7 @@ +@if (newestMeetup$ | async; as meetup) { + +} + @if (posts$ | async; as posts) { @@ -5,7 +9,7 @@
- +
- - + + } @placeholder {
- +
} diff --git a/src/app/features/location/location.component.ts b/src/app/features/location/location.component.ts index 199c1cea..3f6bf6e3 100644 --- a/src/app/features/location/location.component.ts +++ b/src/app/features/location/location.component.ts @@ -16,6 +16,7 @@ import { NotFoundComponent } from '../not-found/not-found.component'; import { TransitionComponent } from '../../components/transition/transition.component'; import { LocationsTabComponent } from '../../core/layout/locations-tab/locations-tab.component'; import { MeetupFooterComponent } from '../../components/meetup-footer/meetup-footer.component'; +import { MeetupsHeaderComponent } from '../../components/meetups-header/meetups-header.component'; @Component({ selector: 'blog-location', @@ -31,6 +32,7 @@ import { MeetupFooterComponent } from '../../components/meetup-footer/meetup-foo MatProgressSpinnerModule, NotFoundComponent, TransitionComponent, + MeetupsHeaderComponent, ], templateUrl: './location.component.html', styleUrl: './location.component.scss', @@ -54,8 +56,9 @@ export class LocationComponent { (post: Post) => post.location === location, (a, b) => this.compareByDate(a, b) ) - ) + ), ); + newestMeetup$ = this.findNewestMeetup$(); authors$: Observable = this.authorsService.getAuthors(); locations$: Observable = this.postsService.getLocations(); @@ -70,6 +73,51 @@ export class LocationComponent { this.navigationService.navigate(page.pageIndex); } + private findNewestMeetup$() { + return combineLatest([ + this.currentPage$, + this.location$, + ]).pipe( + switchMap(([_, loc]) => + this.postsService.getPosts( + undefined, + undefined, + false, + post => this.isMeetupCategoryByLocation(post, loc), + (a, b) => this.compareByDate(a, b) + ).pipe( + map(result => { + const startOfToday = new Date(); + startOfToday.setHours(0, 0, 0, 0); + + return this.findSoonestAfter(result.posts, startOfToday.getTime()); + }) + ) + ) + ); + } + + findSoonestAfter(posts: Post[], minValue: number): Post | null { + let soonest: Post | null = null; + + for (const post of posts) { + const num = new Date(post.date?.trim() ?? '').getTime(); + if ( + num > minValue && + (soonest === null || + num < new Date(soonest.date?.trim() ?? '').getTime()) + ) { + soonest = post; + } + } + + return soonest; + } + + private isMeetupCategoryByLocation(post: Post, location: string | null): boolean { + return (post.category as string) === 'meetups' && post.location === location; + } + private compareByDate(a: Post, b: Post): number { return ( new Date(b.date?.trim() ?? 0).getTime() - diff --git a/src/app/features/meetups/meetups.component.ts b/src/app/features/meetups/meetups.component.ts index 38048458..fc999590 100644 --- a/src/app/features/meetups/meetups.component.ts +++ b/src/app/features/meetups/meetups.component.ts @@ -14,7 +14,6 @@ import { LocationsComponent } from '../../components/locations/locations.compone import { Location } from '../../core/model/locations.model'; import { MeetupFooterComponent } from '../../components/meetup-footer/meetup-footer.component'; - @Component({ selector: 'blog-meetups', standalone: true, @@ -33,9 +32,8 @@ import { MeetupFooterComponent } from '../../components/meetup-footer/meetup-foo styleUrl: './meetups.component.scss', }) export class MeetupsComponent { - locations$: Observable = this.postsService - .getLocations(); - + locations$: Observable = this.postsService.getLocations(); + selectedCategory$ = this.activatedRoute.paramMap.pipe( map(params => params.get('loc') as Location) ); @@ -47,7 +45,7 @@ export class MeetupsComponent { private postsService: PostsService, private navigationService: NavigationService, private activatedRoute: ActivatedRoute, - private router: Router, + private router: Router ) {} navigate(page: PageEvent) { @@ -67,24 +65,30 @@ export class MeetupsComponent { post => this.isMeetupCategory(post), (a, b) => this.compareByDate(a, b) ) - .pipe(map(result => { - const startOfToday = new Date(); - startOfToday.setHours(0, 0, 0, 0); + .pipe( + map(result => { + const startOfToday = new Date(); + startOfToday.setHours(0, 0, 0, 0); - return this.findSoonestAfter(result.posts, startOfToday.getTime()); - })); + return this.findSoonestAfter(result.posts, startOfToday.getTime()); + }) + ); } findSoonestAfter(posts: Post[], minValue: number): Post | null { let soonest: Post | null = null; - + for (const post of posts) { const num = new Date(post.date?.trim() ?? '').getTime(); - if (num > minValue && (soonest === null || num < new Date(soonest.date?.trim() ?? '').getTime())) { + if ( + num > minValue && + (soonest === null || + num < new Date(soonest.date?.trim() ?? '').getTime()) + ) { soonest = post; } } - + return soonest; } diff --git a/src/app/features/not-found/not-found.component.html b/src/app/features/not-found/not-found.component.html index a9da797d..c14e74d7 100644 --- a/src/app/features/not-found/not-found.component.html +++ b/src/app/features/not-found/not-found.component.html @@ -1,6 +1,6 @@

Oeps, nothing to see here!

- +
@@ -8,12 +8,7 @@

Oeps, nothing to see here!

Sorry, the page you're looking for doesn't seem to exist

- Return to homepage + Return to homepage
- diff --git a/src/app/features/not-found/not-found.component.scss b/src/app/features/not-found/not-found.component.scss index 6a91c785..c262fde8 100644 --- a/src/app/features/not-found/not-found.component.scss +++ b/src/app/features/not-found/not-found.component.scss @@ -10,4 +10,3 @@ } } } - diff --git a/src/app/features/post/post.component.html b/src/app/features/post/post.component.html index e1a1d6f4..708c5c9b 100644 --- a/src/app/features/post/post.component.html +++ b/src/app/features/post/post.component.html @@ -28,7 +28,7 @@

{{ post.excerpt }}

- +
diff --git a/src/app/features/post/post.component.scss b/src/app/features/post/post.component.scss index 6e8f4b27..3a6245f6 100644 --- a/src/app/features/post/post.component.scss +++ b/src/app/features/post/post.component.scss @@ -5,7 +5,7 @@ a { text-decoration-color: var(--backbase-primary); text-decoration-thickness: 3px; - text-underline-offset: .25em; + text-underline-offset: 0.25em; } .post { &__header { @@ -102,7 +102,8 @@ a { } line-height: 2; - p, figure { + p, + figure { margin-bottom: 2rem; } diff --git a/src/app/features/post/post.component.ts b/src/app/features/post/post.component.ts index 384255a8..e4ba8570 100644 --- a/src/app/features/post/post.component.ts +++ b/src/app/features/post/post.component.ts @@ -6,10 +6,7 @@ import { } from '@angular/core'; import { DatePipe } from '@angular/common'; import { PostContent } from '../../core/model/post.model'; -import { - RouterLink, - RouterModule, -} from '@angular/router'; +import { RouterLink, RouterModule } from '@angular/router'; import { MarkdownModule } from 'ngx-markdown'; import { AuthorComponent } from '../../components/author/author.component'; import { MatChipsModule } from '@angular/material/chips'; @@ -59,9 +56,7 @@ export class PostComponent { Category = Object.fromEntries(Object.entries(Category)); - constructor( - private htmlInMarkdownService: HtmlInMarkdownService, - ) {} + constructor(private htmlInMarkdownService: HtmlInMarkdownService) {} resolveScripts() { this.htmlInMarkdownService.parseAll(); diff --git a/src/app/features/related-posts/related-posts.component.spec.ts b/src/app/features/related-posts/related-posts.component.spec.ts index 459decfd..debbd658 100644 --- a/src/app/features/related-posts/related-posts.component.spec.ts +++ b/src/app/features/related-posts/related-posts.component.spec.ts @@ -8,10 +8,9 @@ describe('RelatedPostsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [RelatedPostsComponent] - }) - .compileComponents(); - + imports: [RelatedPostsComponent], + }).compileComponents(); + fixture = TestBed.createComponent(RelatedPostsComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/src/app/features/related-posts/related-posts.component.ts b/src/app/features/related-posts/related-posts.component.ts index 6661a300..f921115b 100644 --- a/src/app/features/related-posts/related-posts.component.ts +++ b/src/app/features/related-posts/related-posts.component.ts @@ -9,28 +9,26 @@ import { DividerComponent } from '../../components/divider/divider.component'; @Component({ selector: 'blog-related-posts', standalone: true, - imports: [ - AsyncPipe, - PostItemComponent, - DividerComponent, - ], + imports: [AsyncPipe, PostItemComponent, DividerComponent], templateUrl: './related-posts.component.html', - styleUrl: './related-posts.component.scss' + styleUrl: './related-posts.component.scss', }) export class RelatedPostsComponent { relatedPosts$!: Observable; @Input() set post(post: Post) { - this.relatedPosts$ = this.postsService.getPosts( - 0, - 2, - false, - (_post: Post) => - post.title !== _post.title && - (post.category === _post.category || - post.tags.some(tag => _post.tags.includes(tag))) - ).pipe(map(({ posts }) => posts)); - }; + this.relatedPosts$ = this.postsService + .getPosts( + 0, + 2, + false, + (_post: Post) => + post.title !== _post.title && + (post.category === _post.category || + post.tags.some(tag => _post.tags.includes(tag))) + ) + .pipe(map(({ posts }) => posts)); + } constructor(private postsService: PostsService) {} } diff --git a/src/app/features/search/search.component.ts b/src/app/features/search/search.component.ts index 5cbf3c98..81677fdd 100644 --- a/src/app/features/search/search.component.ts +++ b/src/app/features/search/search.component.ts @@ -43,7 +43,7 @@ export class SearchComponent implements OnInit { constructor( private postsService: PostsService, private router: Router, - private observabilityService: ObservabilityService, + private observabilityService: ObservabilityService ) {} ngOnInit() { @@ -55,15 +55,19 @@ export class SearchComponent implements OnInit { }) ); - this.control.valueChanges.pipe( - filter((search) => - typeof search === 'string' && search.length > 3), - debounceTime(1000), - ).subscribe((search) => { - this.observabilityService.publishEvent({ - 'search.term': search, - }, 'search'); - }) + this.control.valueChanges + .pipe( + filter(search => typeof search === 'string' && search.length > 3), + debounceTime(1000) + ) + .subscribe(search => { + this.observabilityService.publishEvent( + { + 'search.term': search, + }, + 'search' + ); + }); } displayFn(post: Post): string { diff --git a/src/app/markdown.config.ts b/src/app/markdown.config.ts index 9defd985..4fa3ad02 100644 --- a/src/app/markdown.config.ts +++ b/src/app/markdown.config.ts @@ -8,7 +8,7 @@ export default function ( document: Document, htmlInMarkdownService: HtmlInMarkdownService, assetsService: AssetsService, - router: Router, + router: Router ) { markdownService.renderer.link = ( href: string, @@ -87,7 +87,7 @@ export default function ( `; } - + return htmlInMarkdownService.add(html); }; } diff --git a/src/index.html b/src/index.html index 880dcd92..53e480f3 100644 --- a/src/index.html +++ b/src/index.html @@ -5,8 +5,9 @@ Backbase Engineering - - + + + math.abs($color-brightness - $dark-text-brightness), $light, $dark); + @return if( + math.abs($color-brightness - $light-text-brightness) > + math.abs($color-brightness - $dark-text-brightness), + $light, + $dark + ); } -} \ No newline at end of file +} diff --git a/styles/_typography.scss b/styles/_typography.scss index 2ded5a63..4d48cc08 100644 --- a/styles/_typography.scss +++ b/styles/_typography.scss @@ -2,9 +2,10 @@ $backbase-typography: mat.m2-define-typography-config( $font-family: 'Libre Franklin', - $headline-5: mat.m2-define-typography-level(3.5em, 1, 700), /* h1 */ - $headline-6: mat.m2-define-typography-level(1.5em, 2, 700), /* h2 */ - $subtitle-1: mat.m2-define-typography-level(1.2em, 2, 700), /* h3 */ - $body-1: mat.m2-define-typography-level(1em, 2, 700), /* h4 */ - $body-2: mat.m2-define-typography-level(1rem, 1.5, 400), /* body text */ -); \ No newline at end of file + $headline-5: mat.m2-define-typography-level(3.5em, 1, 700), + /* h1 */ $headline-6: mat.m2-define-typography-level(1.5em, 2, 700), + /* h2 */ $subtitle-1: mat.m2-define-typography-level(1.2em, 2, 700), + /* h3 */ $body-1: mat.m2-define-typography-level(1em, 2, 700), + /* h4 */ $body-2: mat.m2-define-typography-level(1rem, 1.5, 400), + /* body text */ +); diff --git a/styles/styles.scss b/styles/styles.scss index 123dbac0..d6926702 100644 --- a/styles/styles.scss +++ b/styles/styles.scss @@ -37,10 +37,10 @@ $text-muted: #999999, $typography: $backbase-typography, $background-color: #{color.mix( - #000000, - map-get($backbase-secondary, 700), - 75% - )}, + #000000, + map-get($backbase-secondary, 700), + 75% + )}, $bg-gradient: ( #69feff, #ff6047, @@ -48,8 +48,8 @@ #ff6047, ), $default: false, - $dark: true, - ) + $dark: true + ); } :root { diff --git a/tools/build-posts/index.js b/tools/build-posts/index.js index 707c8ab3..2d1655bd 100644 --- a/tools/build-posts/index.js +++ b/tools/build-posts/index.js @@ -36,8 +36,9 @@ async function getAuthorRoutes(source) { const utils = await loadEsmModule( '../../dist/utils/esm2022/lib/permalink.mjs' ); - return Object.keys(authors).map(name => - `/people/${utils.getAuthorPermalink(name)}`); + return Object.keys(authors).map( + name => `/people/${utils.getAuthorPermalink(name)}` + ); } return []; } @@ -76,9 +77,7 @@ async function main() { } async function withUtils() { - utils = await loadEsmModule( - '../../dist/utils/esm2022/lib/post-metadata.mjs' - ); + utils = await loadEsmModule('../../dist/utils/esm2022/lib/post-metadata.mjs'); main(); } @@ -87,4 +86,4 @@ withUtils(); function loadEsmModule(modulePath) { return new Function('modulePath', `return import(modulePath);`)(modulePath); -} \ No newline at end of file +} diff --git a/tools/meetup-scaffolder/src/meetup-scaffolder/index_spec.ts b/tools/meetup-scaffolder/src/meetup-scaffolder/index_spec.ts index 32d522a3..f352bfe4 100644 --- a/tools/meetup-scaffolder/src/meetup-scaffolder/index_spec.ts +++ b/tools/meetup-scaffolder/src/meetup-scaffolder/index_spec.ts @@ -7,7 +7,11 @@ const collectionPath = path.join(__dirname, '../collection.json'); describe('meetup-scaffolder', () => { it('works', async () => { const runner = new SchematicTestRunner('schematics', collectionPath); - const tree = await runner.runSchematic('meetup-scaffolder', {}, Tree.empty()); + const tree = await runner.runSchematic( + 'meetup-scaffolder', + {}, + Tree.empty() + ); expect(tree.files).toEqual([]); }); diff --git a/tools/post-scaffolder/src/post-scaffolder/index.ts b/tools/post-scaffolder/src/post-scaffolder/index.ts index 93aaab45..2ec0a56d 100644 --- a/tools/post-scaffolder/src/post-scaffolder/index.ts +++ b/tools/post-scaffolder/src/post-scaffolder/index.ts @@ -23,7 +23,11 @@ export function postScaffolder(options: any): Rule { ...options, stringify, }), - move(normalize(`content/posts/${getPermalink(options.title, false, '', undefined)}`)), + move( + normalize( + `content/posts/${getPermalink(options.title, false, '', undefined)}` + ) + ), ]); const rule = mergeWith(templateSource, MergeStrategy.Default); diff --git a/tools/process-images/index.js b/tools/process-images/index.js index 50e6728b..1100f5a4 100644 --- a/tools/process-images/index.js +++ b/tools/process-images/index.js @@ -36,33 +36,34 @@ const walk = async dir => { Object.entries(SIZES).forEach(([size, [width, height]]) => { if (!(file.endsWith('.gif') || file.endsWith('.webp'))) { Jimp.read(fromPath) - .then(img => { - const distDir = path.join(dir, 'dist', size); + .then(img => { + const distDir = path.join(dir, 'dist', size); - fs.ensureDir(distDir).then(() => { - let callback; - if (height === 0) { - callback = img.resize(width, Jimp.AUTO); - } else { - callback = img.cover(width, height); // crop with same aspect ratio - } + fs.ensureDir(distDir).then(() => { + let callback; + if (height === 0) { + callback = img.resize(width, Jimp.AUTO); + } else { + callback = img.cover(width, height); // crop with same aspect ratio + } - return callback - .clone() // clone resets exif metadata - .quality(60) // set JPEG quality - .write(path.join(distDir, file)); // save + return callback + .clone() // clone resets exif metadata + .quality(60) // set JPEG quality + .write(path.join(distDir, file)); // save + }); + }) + .catch(err => { + console.error(err); }); - }) - .catch(err => { - console.error(err); - }); } else if (file.endsWith('.gif')) { GifUtil.read(fromPath).then(gif => { const distDir = path.join(dir, 'dist', size); fs.ensureDir(distDir).then(() => { gif.width = width; - gif.height = height ?? Math.ceil((width * gif.height) / gif.width); - return GifUtil.write(path.join(distDir, file), gif.frames, gif) + gif.height = + height ?? Math.ceil((width * gif.height) / gif.width); + return GifUtil.write(path.join(distDir, file), gif.frames, gif); }); }); } else if (file.endsWith('.webp')) { @@ -70,7 +71,9 @@ const walk = async dir => { * TODO: decide on a tool that supports webp */ const distDir = path.join(dir, 'dist', size); - fs.ensureDir(distDir).then(() => fs.copy(fromPath, path.join(distDir, file))); + fs.ensureDir(distDir).then(() => + fs.copy(fromPath, path.join(distDir, file)) + ); } }); } diff --git a/tools/publish/index.js b/tools/publish/index.js index 08bd9c26..d0538b78 100644 --- a/tools/publish/index.js +++ b/tools/publish/index.js @@ -20,8 +20,10 @@ function updateMetaDate(filePath) { } async function moveUnpublishedDirectory(sourcePath, destinationRoot) { - if (!fs.existsSync(sourcePath)) { return; } - + if (!fs.existsSync(sourcePath)) { + return; + } + const unpublished = fs.readdirSync(sourcePath); unpublished.forEach(async articlePath => { @@ -48,7 +50,7 @@ async function moveUnpublishedDirectory(sourcePath, destinationRoot) { metaJsonObject.title, false, metaJsonObject.category, - metaJsonObject.date, + metaJsonObject.date ) ); @@ -69,7 +71,7 @@ async function moveUnpublishedDirectory(sourcePath, destinationRoot) { // Remove the "unpublished" directory fs.rmdirSync(unpublishedPath); - + if (isDirectoryEmpty(sourcePath)) { fs.rmdirSync(sourcePath); } @@ -100,8 +102,7 @@ function isDirectoryEmpty(path) { let empty = false; if (fs.existsSync(path)) { const files = fs.readdirSync(path); - empty = !files?.length + empty = !files?.length; } return empty; } - From 03df97fde46e9b04fe7610c9fdd31a5d45ea7a48 Mon Sep 17 00:00:00 2001 From: RobertSenkel Date: Mon, 17 Feb 2025 13:41:08 +0100 Subject: [PATCH 3/4] fix comment --- src/app/core/services/posts.service.ts | 17 +++++++++++++++++ .../features/location/location.component.ts | 19 +------------------ src/app/features/meetups/meetups.component.ts | 19 +------------------ 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/app/core/services/posts.service.ts b/src/app/core/services/posts.service.ts index 50227960..e2ac1262 100644 --- a/src/app/core/services/posts.service.ts +++ b/src/app/core/services/posts.service.ts @@ -142,6 +142,23 @@ export class PostsService { ); } + findSoonestPostAfter(posts: Post[], minValue: number): Post | null { + let soonest: Post | null = null; + + for (const post of posts) { + const num = new Date(post.date?.trim() ?? '').getTime(); + if ( + num > minValue && + (soonest === null || + num < new Date(soonest.date?.trim() ?? '').getTime()) + ) { + soonest = post; + } + } + + return soonest; + } + private decoratePost( post: Post | PostContent, authors: AuthorsList diff --git a/src/app/features/location/location.component.ts b/src/app/features/location/location.component.ts index 3f6bf6e3..1920838b 100644 --- a/src/app/features/location/location.component.ts +++ b/src/app/features/location/location.component.ts @@ -90,30 +90,13 @@ export class LocationComponent { const startOfToday = new Date(); startOfToday.setHours(0, 0, 0, 0); - return this.findSoonestAfter(result.posts, startOfToday.getTime()); + return this.postsService.findSoonestPostAfter(result.posts, startOfToday.getTime()); }) ) ) ); } - findSoonestAfter(posts: Post[], minValue: number): Post | null { - let soonest: Post | null = null; - - for (const post of posts) { - const num = new Date(post.date?.trim() ?? '').getTime(); - if ( - num > minValue && - (soonest === null || - num < new Date(soonest.date?.trim() ?? '').getTime()) - ) { - soonest = post; - } - } - - return soonest; - } - private isMeetupCategoryByLocation(post: Post, location: string | null): boolean { return (post.category as string) === 'meetups' && post.location === location; } diff --git a/src/app/features/meetups/meetups.component.ts b/src/app/features/meetups/meetups.component.ts index fc999590..83c6f234 100644 --- a/src/app/features/meetups/meetups.component.ts +++ b/src/app/features/meetups/meetups.component.ts @@ -70,28 +70,11 @@ export class MeetupsComponent { const startOfToday = new Date(); startOfToday.setHours(0, 0, 0, 0); - return this.findSoonestAfter(result.posts, startOfToday.getTime()); + return this.postsService.findSoonestPostAfter(result.posts, startOfToday.getTime()); }) ); } - findSoonestAfter(posts: Post[], minValue: number): Post | null { - let soonest: Post | null = null; - - for (const post of posts) { - const num = new Date(post.date?.trim() ?? '').getTime(); - if ( - num > minValue && - (soonest === null || - num < new Date(soonest.date?.trim() ?? '').getTime()) - ) { - soonest = post; - } - } - - return soonest; - } - private getAllMeetups$() { return this.currentPage$.pipe( switchMap(page => From a37b0558c7d81739eb745c3f51b564cba468a2f8 Mon Sep 17 00:00:00 2001 From: RobertSenkel Date: Mon, 17 Feb 2025 16:42:00 +0100 Subject: [PATCH 4/4] fix comment --- src/app/core/model/categories.model.ts | 2 +- src/app/features/location/location.component.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/core/model/categories.model.ts b/src/app/core/model/categories.model.ts index e95de688..0b6e359a 100644 --- a/src/app/core/model/categories.model.ts +++ b/src/app/core/model/categories.model.ts @@ -6,7 +6,7 @@ export enum Category { 'sdlc' = 'SDLC', 'career' = 'Career', 'principles' = 'Principles', - 'meetups' = 'Meetups', + 'meetups' = 'meetups', 'qa' = 'Quality Assurance', 'mobile' = 'Mobile', } diff --git a/src/app/features/location/location.component.ts b/src/app/features/location/location.component.ts index 1920838b..7f4f3275 100644 --- a/src/app/features/location/location.component.ts +++ b/src/app/features/location/location.component.ts @@ -17,6 +17,7 @@ import { TransitionComponent } from '../../components/transition/transition.comp import { LocationsTabComponent } from '../../core/layout/locations-tab/locations-tab.component'; import { MeetupFooterComponent } from '../../components/meetup-footer/meetup-footer.component'; import { MeetupsHeaderComponent } from '../../components/meetups-header/meetups-header.component'; +import { Category } from '../../core/model/categories.model'; @Component({ selector: 'blog-location', @@ -98,7 +99,7 @@ export class LocationComponent { } private isMeetupCategoryByLocation(post: Post, location: string | null): boolean { - return (post.category as string) === 'meetups' && post.location === location; + return post.category === Category.meetups && post.location === location; } private compareByDate(a: Post, b: Post): number {