markdown img insert work as intended
This commit is contained in:
parent
28609a6492
commit
6467e21019
@ -8,7 +8,7 @@ import {
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
} from 'class-validator';
|
||||
import { ArticleStatus, LiveBlogStatus } from './entities';
|
||||
import { ArticleStatus, LiveBlogStatus, ImagePosition, ImageSize } from './entities';
|
||||
|
||||
export class CreateArticleDto {
|
||||
@IsString()
|
||||
@ -49,6 +49,14 @@ export class CreateArticleDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
categoryId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImagePosition)
|
||||
imagePosition?: ImagePosition;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImageSize)
|
||||
imageSize?: ImageSize;
|
||||
}
|
||||
|
||||
export class UpdateArticleDto {
|
||||
@ -92,6 +100,14 @@ export class UpdateArticleDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
categoryId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImagePosition)
|
||||
imagePosition?: ImagePosition;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImageSize)
|
||||
imageSize?: ImageSize;
|
||||
}
|
||||
|
||||
export class FindArticlesDto {
|
||||
@ -157,6 +173,14 @@ export class CreateLiveBlogDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
featuredImage?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImagePosition)
|
||||
imagePosition?: ImagePosition;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImageSize)
|
||||
imageSize?: ImageSize;
|
||||
}
|
||||
|
||||
export class UpdateLiveBlogDto {
|
||||
@ -195,6 +219,14 @@ export class UpdateLiveBlogDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
featuredImage?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImagePosition)
|
||||
imagePosition?: ImagePosition;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ImageSize)
|
||||
imageSize?: ImageSize;
|
||||
}
|
||||
|
||||
export class CreateLiveBlogUpdateDto {
|
||||
|
||||
@ -36,6 +36,19 @@ export enum LiveBlogStatus {
|
||||
ARCHIVED = 'archived',
|
||||
}
|
||||
|
||||
export enum ImagePosition {
|
||||
TOP = 'top',
|
||||
LEFT = 'left',
|
||||
RIGHT = 'right',
|
||||
NONE = 'none',
|
||||
}
|
||||
|
||||
export enum ImageSize {
|
||||
SMALL = 'small',
|
||||
MEDIUM = 'medium',
|
||||
LARGE = 'large',
|
||||
}
|
||||
|
||||
@Entity('authors')
|
||||
export class Author {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
@ -114,6 +127,18 @@ export class Article {
|
||||
@Column({ default: '' })
|
||||
featuredImage: string;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
default: 'top',
|
||||
})
|
||||
imagePosition: ImagePosition;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
default: 'medium',
|
||||
})
|
||||
imageSize: ImageSize;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
default: '[]',
|
||||
@ -189,6 +214,18 @@ export class LiveBlog {
|
||||
@Column({ default: '' })
|
||||
featuredImage: string;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
default: 'top',
|
||||
})
|
||||
imagePosition: ImagePosition;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
default: 'medium',
|
||||
})
|
||||
imageSize: ImageSize;
|
||||
|
||||
@Column({ default: 0 })
|
||||
viewCount: number;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { lastValueFrom } from 'rxjs';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { LiveBlogService } from './live-blog.service';
|
||||
import { CreateArticleDto, CreateLiveBlogDto } from './articles.dto';
|
||||
import { ArticleStatus, LiveBlogStatus } from './entities';
|
||||
import { ArticleStatus, LiveBlogStatus, ImagePosition, ImageSize } from './entities';
|
||||
|
||||
interface StrapiArticle {
|
||||
id: number;
|
||||
@ -19,6 +19,8 @@ interface StrapiArticle {
|
||||
updatedAt: string;
|
||||
img?: any;
|
||||
media?: any[];
|
||||
imagePosition?: string;
|
||||
imageSize?: string;
|
||||
}
|
||||
|
||||
interface StrapiLiveBlog {
|
||||
@ -33,6 +35,8 @@ interface StrapiLiveBlog {
|
||||
updatedAt: string;
|
||||
img?: any;
|
||||
media?: any[];
|
||||
imagePosition?: string;
|
||||
imageSize?: string;
|
||||
}
|
||||
|
||||
interface StrapiResponse<T> {
|
||||
@ -156,6 +160,8 @@ export class StrapiService {
|
||||
: ArticleStatus.DRAFT,
|
||||
tags: [],
|
||||
featuredImage: imageUrl,
|
||||
imagePosition: (strapiArticle.imagePosition || 'top') as ImagePosition,
|
||||
imageSize: (strapiArticle.imageSize || 'medium') as ImageSize,
|
||||
};
|
||||
|
||||
await this.articlesService.syncFromStrapi(
|
||||
@ -215,6 +221,8 @@ export class StrapiService {
|
||||
status,
|
||||
tags: [],
|
||||
featuredImage: imageUrl,
|
||||
imagePosition: (strapiArticle.imagePosition || 'top') as ImagePosition,
|
||||
imageSize: (strapiArticle.imageSize || 'medium') as ImageSize,
|
||||
};
|
||||
|
||||
await this.articlesService.syncFromStrapi(
|
||||
@ -277,6 +285,8 @@ export class StrapiService {
|
||||
slug: strapiLiveBlog.slug,
|
||||
status: this.mapStrapiStatusToLiveBlogStatus(strapiLiveBlog.status),
|
||||
featuredImage: imageUrl,
|
||||
imagePosition: (strapiLiveBlog.imagePosition || 'top') as ImagePosition,
|
||||
imageSize: (strapiLiveBlog.imageSize || 'medium') as ImageSize,
|
||||
};
|
||||
|
||||
await this.liveBlogService.syncFromStrapi(
|
||||
@ -330,6 +340,8 @@ export class StrapiService {
|
||||
slug: strapiLiveBlog.slug,
|
||||
status,
|
||||
featuredImage: imageUrl,
|
||||
imagePosition: (strapiLiveBlog.imagePosition || 'top') as ImagePosition,
|
||||
imageSize: (strapiLiveBlog.imageSize || 'medium') as ImageSize,
|
||||
};
|
||||
|
||||
await this.liveBlogService.syncFromStrapi(
|
||||
|
||||
@ -39,6 +39,16 @@
|
||||
"videos",
|
||||
"audios"
|
||||
]
|
||||
},
|
||||
"imagePosition": {
|
||||
"type": "enumeration",
|
||||
"enum": ["top", "left", "right", "none"],
|
||||
"default": "top"
|
||||
},
|
||||
"imageSize": {
|
||||
"type": "enumeration",
|
||||
"enum": ["small", "medium", "large"],
|
||||
"default": "medium"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1493
frontend/package-lock.json
generated
1493
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,9 @@
|
||||
"@tanstack/react-router": "^1.144.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"react-dom": "^19.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import * as api from '@/lib/api'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
export function ArticleDetailComponent({ id }: { id: string }) {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
@ -65,8 +67,28 @@ export function ArticleDetailComponent({ id }: { id: string }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{data.featuredImage && (
|
||||
<div className="relative w-full h-64 md:h-96 mb-8">
|
||||
{data.featuredImage && data.imagePosition !== 'none' && (
|
||||
<div className={`relative ${
|
||||
data.imagePosition === 'top'
|
||||
? 'w-full h-64 md:h-96 mb-8'
|
||||
: data.imagePosition === 'left'
|
||||
? 'float-none md:float-left mr-0 md:mr-6 mb-4 w-full md:w-auto'
|
||||
: data.imagePosition === 'right'
|
||||
? 'float-none md:float-right ml-0 md:ml-6 mb-4 w-full md:w-auto'
|
||||
: ''
|
||||
} ${
|
||||
data.imagePosition === 'top'
|
||||
? data.imageSize === 'small'
|
||||
? 'h-32'
|
||||
: data.imageSize === 'medium'
|
||||
? 'h-48'
|
||||
: 'h-64'
|
||||
: data.imageSize === 'small'
|
||||
? 'w-full md:w-48 h-32'
|
||||
: data.imageSize === 'medium'
|
||||
? 'w-full md:w-64 h-48'
|
||||
: 'w-full md:w-96 h-64'
|
||||
}`}>
|
||||
<img
|
||||
src={data.featuredImage}
|
||||
alt={data.title}
|
||||
@ -90,8 +112,23 @@ export function ArticleDetailComponent({ id }: { id: string }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="prose prose-slate max-w-none">
|
||||
<p className="text-lg leading-relaxed mb-6">{data.content}</p>
|
||||
<div className="prose prose-slate max-w-none clear-both md:clear-none">
|
||||
<div className="text-lg leading-relaxed mb-6">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
img: (props) => (
|
||||
<img
|
||||
{...props}
|
||||
className="max-w-full h-auto rounded-lg my-4"
|
||||
alt={props.alt || 'Article image'}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
>
|
||||
{data.content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.tags && Array.isArray(data.tags) && data.tags.length > 0 && (
|
||||
|
||||
@ -11,6 +11,8 @@ export interface Article {
|
||||
excerpt: string | null;
|
||||
slug: string;
|
||||
featuredImage: string;
|
||||
imagePosition: 'top' | 'left' | 'right' | 'none';
|
||||
imageSize: 'small' | 'medium' | 'large';
|
||||
tags: string[];
|
||||
status: 'draft' | 'published' | 'archived';
|
||||
views: number;
|
||||
@ -126,6 +128,9 @@ export interface LiveBlog {
|
||||
strapiId: string | null;
|
||||
authorId: string | null;
|
||||
categoryId: string | null;
|
||||
featuredImage: string;
|
||||
imagePosition: 'top' | 'left' | 'right' | 'none';
|
||||
imageSize: 'small' | 'medium' | 'large';
|
||||
viewCount: number;
|
||||
author?: {
|
||||
id: string;
|
||||
|
||||
2759
package-lock.json
generated
2759
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user