diff --git a/package.json b/package.json index 4a5fd8dc71..4d9a739550 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~1.7.0", "postcss": "^8.4.17", + "prettier": "^3.4.2", "shx": "^0.3.3", "tailwindcss": "^3.1.8", "ts-node": "^10.2.1", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7ac3b09589..4210b7cdb6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,14 +15,14 @@ import { filter, pairwise, startWith } from 'rxjs'; declare const gtag: ( command: string, action: string, - config: { page_path: string } + config: { page_path: string }, ) => void; @Component({ - selector: 'app-root', - imports: [LayoutComponent], - template: ``, - changeDetection: ChangeDetectionStrategy.OnPush + selector: 'app-root', + imports: [LayoutComponent], + template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent implements OnInit { private router = inject(Router); @@ -46,12 +46,12 @@ export class AppComponent implements OnInit { .pipe( filter((event) => event instanceof NavigationEnd), startWith(null), - pairwise() + pairwise(), ) .subscribe((events) => { if (!this.platformService.isServer && environment.production) { this.trackService.sendTrack( - (events[0] as NavigationEnd | null)?.url || '' + (events[0] as NavigationEnd | null)?.url || '', ); gtag('event', 'page_view', { page_path: (events[1] as NavigationEnd | null)?.url || '', diff --git a/src/app/blog/blog-archives/blog-archives.component.ts b/src/app/blog/blog-archives/blog-archives.component.ts index 4b10099f19..bb190322fc 100644 --- a/src/app/blog/blog-archives/blog-archives.component.ts +++ b/src/app/blog/blog-archives/blog-archives.component.ts @@ -14,44 +14,42 @@ import { getPagePosts } from '../get-page-posts'; const PAGE_SIZE = 10; @Component({ - selector: 'app-blog-archives', - template: ` + selector: 'app-blog-archives', + template: ` @for (yearPosts of posts(); track yearPosts.year) { - - -

{{ yearPosts.year }}

-

年 (共 {{ yearPosts.postCount }} 篇文章)

-
- - @for (post of yearPosts.posts; track post.slug) { - - - - {{ post.title }} - - - - - - - -
-
- - - - read_more - 繼續閱讀 - - -
- - } } + +

{{ yearPosts.year }}

+

年 (共 {{ yearPosts.postCount }} 篇文章)

+
+ + @for (post of yearPosts.posts; track post.slug) { + + + {{ post.title }} + + + + + + + +
+
+ + + + read_more + 繼續閱讀 + + +
+ } + } `, - styles: ``, - imports: [ - MatToolbarModule, - MatCardModule, - RouterLink, - BlogPostSubtitleComponent, - MatButtonModule, - MatIconModule, - PaginationComponent, - PostDateAsPathPipe, - ], - changeDetection: ChangeDetectionStrategy.OnPush + styles: ``, + imports: [ + MatToolbarModule, + MatCardModule, + RouterLink, + BlogPostSubtitleComponent, + MatButtonModule, + MatIconModule, + PaginationComponent, + PostDateAsPathPipe, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BlogArchivesComponent { protected currentPage = getRouteParam( (paramMap) => +(paramMap.get('page') || 1), - 1 + 1, ); protected totalPosts = getRouteData( (data) => data.posts as Array, - [] + [], ); protected yearPostsCount = computed(() => { - return this.totalPosts().reduce((prev, curr) => { - const year = curr.date.substr(0, 4); - if (!prev[year]) { - prev[year] = 0; - } - prev[year] += 1; - return prev; - }, {} as { [key: string]: number }); + return this.totalPosts().reduce( + (prev, curr) => { + const year = curr.date.substr(0, 4); + if (!prev[year]) { + prev[year] = 0; + } + prev[year] += 1; + return prev; + }, + {} as { [key: string]: number }, + ); }); protected posts = computed(() => { @@ -101,22 +102,25 @@ export class BlogArchivesComponent { const pageNum = this.currentPage(); const pagePosts = getPagePosts(pageNum, PAGE_SIZE, totalPosts); const yearPostsCount = this.yearPostsCount(); - return pagePosts.reduce((prev, curr) => { - const year = curr.date.slice(0, 4); - - const yearPosts = prev.find((item) => item.year === year); - if (!yearPosts) { - prev.push({ - year, - postCount: yearPostsCount[year], - posts: [curr], - }); - } else { - yearPosts.posts.push(curr); - } - - return prev; - }, [] as { year: string; postCount: number; posts: PostMetaWithSlug[] }[]); + return pagePosts.reduce( + (prev, curr) => { + const year = curr.date.slice(0, 4); + + const yearPosts = prev.find((item) => item.year === year); + if (!yearPosts) { + prev.push({ + year, + postCount: yearPostsCount[year], + posts: [curr], + }); + } else { + yearPosts.posts.push(curr); + } + + return prev; + }, + [] as { year: string; postCount: number; posts: PostMetaWithSlug[] }[], + ); }); protected totalPage = computed(() => { diff --git a/src/app/blog/blog-categories/blog-categories-posts.component.ts b/src/app/blog/blog-categories/blog-categories-posts.component.ts index 5660506473..3d3be2a245 100644 --- a/src/app/blog/blog-categories/blog-categories-posts.component.ts +++ b/src/app/blog/blog-categories/blog-categories-posts.component.ts @@ -1,4 +1,10 @@ -import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + inject, +} from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; @@ -16,41 +22,39 @@ import { getPagePosts } from '../get-page-posts'; const PAGE_SIZE = 10; @Component({ - selector: 'app-blog-categories-posts', - template: ` + selector: 'app-blog-categories-posts', + template: `

{{ categorySlug() || '' | unslugify }}

分類 (共 {{ categoryPostsCount() }} 篇文章)

@for (post of posts(); track post.slug) { + + + {{ post.title }} + - - - {{ post.title }} - + + + - - - - - -
-
- - - - read_more - 繼續閱讀 - - -
+ +
+
+ + + read_more + 繼續閱讀 + + +
} @@ -61,41 +65,41 @@ const PAGE_SIZE = 10; > `, - styles: ``, - imports: [ - MatToolbarModule, - MatCardModule, - RouterLink, - BlogPostSubtitleComponent, - MatButtonModule, - MatIconModule, - PaginationComponent, - UnslugifyPipe, - PostDateAsPathPipe, - ], - changeDetection: ChangeDetectionStrategy.OnPush + styles: ``, + imports: [ + MatToolbarModule, + MatCardModule, + RouterLink, + BlogPostSubtitleComponent, + MatButtonModule, + MatIconModule, + PaginationComponent, + UnslugifyPipe, + PostDateAsPathPipe, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BlogCategoriesPostsComponent { private siteMetaService = inject(SiteMetaService); protected categorySlug = getRouteParam( (paramMap) => paramMap.get('category-slug'), - '' + '', ); protected currentPage = getRouteParam( (paramMap) => +(paramMap.get('page') || 1), - 1 + 1, ); protected categoryPosts = getRouteData( (data) => data.posts as Array, - [] + [], ); protected categoryPostsCount = computed(() => this.categoryPosts().length); protected posts = computed(() => - getPagePosts(this.currentPage(), PAGE_SIZE, this.categoryPosts()) + getPagePosts(this.currentPage(), PAGE_SIZE, this.categoryPosts()), ); protected totalPage = computed(() => - Math.ceil(this.categoryPostsCount() / PAGE_SIZE) + Math.ceil(this.categoryPostsCount() / PAGE_SIZE), ); private _updateMetaEffect = effect(() => { diff --git a/src/app/blog/blog-categories/blog-categories.component.ts b/src/app/blog/blog-categories/blog-categories.component.ts index efc2b54c23..a7d55a1b90 100644 --- a/src/app/blog/blog-categories/blog-categories.component.ts +++ b/src/app/blog/blog-categories/blog-categories.component.ts @@ -1,5 +1,10 @@ import { KeyValuePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, effect, inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + effect, + inject, +} from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterLink } from '@angular/router'; import { getRouteData } from 'src/app/site-common/route-utils'; @@ -8,8 +13,8 @@ import { SiteMetaService } from '../../site-common/site-meta.service'; import { SlugifyPipe } from '../../site-common/slugify.pipe'; @Component({ - selector: 'app-blog-categories', - template: ` + selector: 'app-blog-categories', + template: ` 分類 @@ -20,31 +25,31 @@ import { SlugifyPipe } from '../../site-common/slugify.pipe'; `, - styles: ``, - imports: [MatCardModule, RouterLink, KeyValuePipe, SlugifyPipe], - changeDetection: ChangeDetectionStrategy.OnPush + styles: ``, + imports: [MatCardModule, RouterLink, KeyValuePipe, SlugifyPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BlogCategoriesComponent { private siteMetaService = inject(SiteMetaService); protected categories = getRouteData( (data) => data.categories as { [key: string]: Array }, - {} + {}, ); private _updateMetaEffect = effect(() => { diff --git a/src/app/blog/blog-layout.component.ts b/src/app/blog/blog-layout.component.ts index 5cae1228e2..795bbe67bb 100644 --- a/src/app/blog/blog-layout.component.ts +++ b/src/app/blog/blog-layout.component.ts @@ -5,8 +5,8 @@ import { RouterOutlet } from '@angular/router'; import { findMainContentContainer, scrollTo } from '../../utils'; @Component({ - selector: 'app-blog-layout', - template: ` + selector: 'app-blog-layout', + template: `
@@ -23,10 +23,9 @@ import { findMainContentContainer, scrollTo } from '../../utils';
`, - imports: [RouterOutlet, MatButtonModule, MatIconModule] + imports: [RouterOutlet, MatButtonModule, MatIconModule], }) export class BlogLayoutComponent { - goTop(contentElement: HTMLElement) { if (contentElement) { const containerElement = findMainContentContainer(contentElement); diff --git a/src/app/blog/blog-posts/blog-post/blog-post-toc.component.ts b/src/app/blog/blog-posts/blog-post/blog-post-toc.component.ts index 8d65b740ed..7167da0efa 100644 --- a/src/app/blog/blog-posts/blog-post/blog-post-toc.component.ts +++ b/src/app/blog/blog-posts/blog-post/blog-post-toc.component.ts @@ -23,36 +23,38 @@ interface Heading { const HEADINGS_CACHE_KEY = makeStateKey('POST_TOC'); @Component({ - selector: 'app-blog-post-toc', - template: `
+ selector: 'app-blog-post-toc', + template: `
文章大綱
@for (head of headings(); track head.level) { - + + {{ head.text }} + +
}
`, - styles: ` + styles: ` :host { display: block; position: fixed; @@ -82,9 +84,10 @@ const HEADINGS_CACHE_KEY = makeStateKey('POST_TOC'); } } } - }`, - imports: [], - changeDetection: ChangeDetectionStrategy.OnPush + } + `, + imports: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BlogPostTocComponent implements OnDestroy { private transferState = inject(TransferState); @@ -104,8 +107,8 @@ export class BlogPostTocComponent implements OnDestroy { switchMap((contentElement) => this.contentObserver.observe(contentElement).pipe( map(() => contentElement), - startWith(contentElement) - ) + startWith(contentElement), + ), ), map((element) => ({ cacheHeadings: this.getCacheHeadings(), element })), map((data) => { @@ -133,9 +136,9 @@ export class BlogPostTocComponent implements OnDestroy { return data; }), switchMap((data) => - this.getContentTocHeadings(data.headings, data.element) + this.getContentTocHeadings(data.headings, data.element), ), - startWith(this.getCacheHeadings()) + startWith(this.getCacheHeadings()), ); protected headings = toSignal(this.headings$, { initialValue: [] }); @@ -167,7 +170,7 @@ export class BlogPostTocComponent implements OnDestroy { // client 計算要 active 的 element const containerElement = element.closest( - '.mat-drawer-content.main-content' + '.mat-drawer-content.main-content', ); const options = { root: containerElement, @@ -187,20 +190,20 @@ export class BlogPostTocComponent implements OnDestroy { visibleElements = [...visibleElements, entry.target as HTMLElement]; } else { visibleElements = visibleElements.filter( - (element) => element !== entry.target + (element) => element !== entry.target, ); } }); if (visibleElements.length > 0) { const visibleElement = visibleElements.sort( - (a, b) => a.offsetTop - b.offsetTop + (a, b) => a.offsetTop - b.offsetTop, )[0]; subscriber.next( headings.map((head) => ({ ...head, active: head.element === visibleElement, - })) + })), ); } }, options); @@ -217,7 +220,9 @@ export class BlogPostTocComponent implements OnDestroy { result.push({ text: head.textContent || '', level: +head.tagName.replace(/h(.)/i, '$1'), - element: this.platformService.isServer ? undefined : head as HTMLElement, + element: this.platformService.isServer + ? undefined + : (head as HTMLElement), active: false, }); }); diff --git a/src/app/blog/blog-posts/blog-post/blog-post.component.ts b/src/app/blog/blog-posts/blog-post/blog-post.component.ts index b646600856..eb2256ad80 100644 --- a/src/app/blog/blog-posts/blog-post/blog-post.component.ts +++ b/src/app/blog/blog-posts/blog-post/blog-post.component.ts @@ -5,7 +5,7 @@ import { computed, effect, inject, - viewChild + viewChild, } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatRippleModule } from '@angular/material/core'; @@ -30,7 +30,7 @@ import { BlogPostTocComponent } from './blog-post-toc.component'; const findPreviousPost = (posts: PostMetaWithSlug[], target: MarkdownMeta) => { const found = posts.filter( - (post) => new Date(post.date) < new Date(target.date) + (post) => new Date(post.date) < new Date(target.date), ); if (found) { return found[found.length - 1]; @@ -40,7 +40,7 @@ const findPreviousPost = (posts: PostMetaWithSlug[], target: MarkdownMeta) => { const findNextPost = (posts: PostMetaWithSlug[], target: MarkdownMeta) => { const found = posts.filter( - (post) => new Date(post.date) > new Date(target.date) + (post) => new Date(post.date) > new Date(target.date), ); if (found) { return found[0]; @@ -48,9 +48,8 @@ const findNextPost = (posts: PostMetaWithSlug[], target: MarkdownMeta) => { return null; }; @Component({ - selector: 'app-blog-post', - template: `@if (postMeta(); as postMeta) { - + selector: 'app-blog-post', + template: `@if (postMeta(); as postMeta) {
@@ -75,13 +74,13 @@ const findNextPost = (posts: PostMetaWithSlug[], target: MarkdownMeta) => {
@for (tag of postMeta.tags; track tag) { - - {{ tag }} - + + {{ tag }} + }
@@ -97,61 +96,58 @@ const findNextPost = (posts: PostMetaWithSlug[], target: MarkdownMeta) => {