blog/frontend/src/lib/post/adapter/presenter/postInfoViewModel.ts
SquidSpirit 3cb69f6e7c
All checks were successful
Frontend CI / build (push) Successful in 1m8s
BLOG-45 Post content page (#67)
### Description

- Implement the content page
  - Parse markdown formant content to html by `markdown-it`
  - Use `sanitize-html` to prevent from XSS attack
  - Style the html with `tailwindcss-typography`
- Fix the issue when backend parse the password to url
- Fix and make the post info list from backend always sorted by id

### Package Changes

### Rust

```toml
percent-encoding = "2.3.1"
```

### Node

```json
{
  "@types/markdown-it": "^14.1.2",
  "@types/sanitize-html": "^2.16.0",
  "markdown-it": "^14.1.0",
  "sanitize-html": "^2.17.0"
}
```

### Screenshots

|Desktop|Mobile|
|-|-|
|![image.png](/attachments/0ec5718a-f804-432f-8e4b-e9dc22c080d2)|![beta.squidspirit.com_post(iPhone 12 Pro) (1).png](/attachments/b30d1b96-d4a4-4b2b-b9bd-90fd2592ab52)|

### Reference

Resolves #45

### Checklist

- [x] A milestone is set
- [x] The related issuse has been linked to this branch

Reviewed-on: #67
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
2025-07-24 22:20:58 +08:00

77 lines
2.0 KiB
TypeScript

import {
LabelViewModel,
type DehydratedLabelProps
} from '$lib/post/adapter/presenter/labelViewModel';
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
export class PostInfoViewModel {
readonly id: number;
readonly title: string;
readonly description: string;
readonly previewImageUrl: URL;
readonly labels: readonly LabelViewModel[];
readonly publishedTime: Date;
private constructor(props: {
id: number;
title: string;
description: string;
previewImageUrl: URL;
labels: readonly LabelViewModel[];
publishedTime: Date;
}) {
this.id = props.id;
this.title = props.title;
this.description = props.description;
this.previewImageUrl = props.previewImageUrl;
this.labels = props.labels;
this.publishedTime = props.publishedTime;
}
static fromEntity(postInfo: PostInfo): PostInfoViewModel {
return new PostInfoViewModel({
id: postInfo.id,
title: postInfo.title,
description: postInfo.description,
previewImageUrl: postInfo.previewImageUrl,
labels: postInfo.labels.map((label) => LabelViewModel.fromEntity(label)),
publishedTime: postInfo.publishedTime
});
}
static rehydrate(props: DehydratedPostInfoProps): PostInfoViewModel {
return new PostInfoViewModel({
id: props.id,
title: props.title,
description: props.description,
previewImageUrl: new URL(props.previewImageUrl),
labels: props.labels.map((label) => LabelViewModel.rehydrate(label)),
publishedTime: new Date(props.publishedTime)
});
}
get formattedPublishedTime(): string {
return this.publishedTime.toISOString().slice(0, 10);
}
dehydrate(): DehydratedPostInfoProps {
return {
id: this.id,
title: this.title,
description: this.description,
previewImageUrl: this.previewImageUrl.href,
labels: this.labels.map((label) => label.dehydrate()),
publishedTime: this.publishedTime.getTime()
};
}
}
export interface DehydratedPostInfoProps {
id: number;
title: string;
description: string;
previewImageUrl: string;
labels: DehydratedLabelProps[];
publishedTime: number;
}