feat: add article's page
This commit is contained in:
parent
7c9157384e
commit
e8fa391b26
|
|
@ -11,11 +11,15 @@
|
|||
"test": "yarn tsc && yarn lint && yarn test:unit && yarn test:e2e"
|
||||
},
|
||||
"dependencies": {
|
||||
"dompurify": "^2.1.1",
|
||||
"marked": "^1.2.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-beta.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dompurify": "^2.0.4",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.3.0",
|
||||
"@typescript-eslint/parser": "^4.2.0",
|
||||
"@vue/compiler-sfc": "^3.0.0-rc.1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<div class="card-block">
|
||||
<p class="card-text">
|
||||
{{ comment.body }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<RouterLink
|
||||
:to="`/profile/${comment.author.username}`"
|
||||
class="comment-author"
|
||||
>
|
||||
<img
|
||||
:src="comment.author.image"
|
||||
class="comment-author-img"
|
||||
>
|
||||
</RouterLink>
|
||||
|
||||
|
||||
|
||||
<RouterLink
|
||||
:to="`/profile/${comment.author.username}`"
|
||||
class="comment-author"
|
||||
>
|
||||
{{ comment.author.username }}
|
||||
</RouterLink>
|
||||
|
||||
<span class="date-posted">{{ (new Date(comment.createdAt)).toLocaleDateString() }}</span>
|
||||
|
||||
<span class="mod-options">
|
||||
<i class="ion-edit" />
|
||||
<i class="ion-trash-a" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticleMeta',
|
||||
props: {
|
||||
comment: { type: Object as PropType<ArticleComment>, required: true },
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class="article-meta">
|
||||
<a href=""><img :src="article.author?.image"></a>
|
||||
|
||||
<div class="info">
|
||||
<RouterLink
|
||||
:to="`/profile/${article.author?.username}`"
|
||||
class="author"
|
||||
>
|
||||
{{ article.author?.username }}
|
||||
</RouterLink>
|
||||
|
||||
<span class="date">{{ (new Date(article.createdAt)).toLocaleDateString() }}</span>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
<i class="ion-plus-round" />
|
||||
|
||||
Follow {{ article.author?.username }}
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
<i class="ion-heart" />
|
||||
|
||||
{{ article.favorited ? "Unfavorite" : "Favorite" }} Article
|
||||
<span class="counter">({{ article.favoritesCount }})</span>
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<RouterLink
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
:to="`/editor/${article.slug}`"
|
||||
>
|
||||
<i class="ion-edit" /> Edit Article
|
||||
</RouterLink>
|
||||
|
||||
|
||||
|
||||
<button class="btn btn-outline-danger btn-sm">
|
||||
<i class="ion-trash-a" /> Delete Article
|
||||
</button>
|
||||
//
|
||||
<button
|
||||
class="btn btn-outline-danger btn-sm disabled"
|
||||
disabled
|
||||
>
|
||||
<i class="ion-trash-a" /> Delete Article
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticleMeta',
|
||||
props: {
|
||||
article: { type: Object as PropType<Article>, required: true },
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<div class="article-page">
|
||||
<div class="banner">
|
||||
<div class="container">
|
||||
<h1>{{ article.title }}</h1>
|
||||
|
||||
<ArticleMeta :article="article" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container page">
|
||||
<div class="row article-content">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
class="col-md-12"
|
||||
v-html="articleHandledBody"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="article-actions">
|
||||
<ArticleMeta :article="article" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-8 offset-md-2">
|
||||
<form class="card comment-form">
|
||||
<div class="card-block">
|
||||
<textarea
|
||||
class="form-control"
|
||||
placeholder="Write a comment..."
|
||||
rows="3"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<img
|
||||
:src="article.author?.image"
|
||||
class="comment-author-img"
|
||||
>
|
||||
<button class="btn btn-sm btn-primary">
|
||||
Post Comment
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ArticleComment
|
||||
v-for="comment in comments"
|
||||
:key="comment.id"
|
||||
:comment="comment"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import md2html from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
import { getArticle } from '../services/article/getArticle'
|
||||
import { getCommentsByArticle } from '../services'
|
||||
|
||||
import ArticleMeta from '../components/ArticleMeta.vue'
|
||||
import ArticleComment from '../components/ArticleComment.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Article',
|
||||
components: {
|
||||
ArticleMeta,
|
||||
ArticleComment,
|
||||
},
|
||||
setup () {
|
||||
const route = useRoute()
|
||||
const slug = route.params.slug as string
|
||||
const article = reactive<Article>({} as Article)
|
||||
const comments = ref<ArticleComment[]>([])
|
||||
|
||||
watchEffect(async () => {
|
||||
const articleData = await getArticle(slug)
|
||||
Object.assign(article, articleData)
|
||||
|
||||
const commentsData = await getCommentsByArticle(slug)
|
||||
comments.value = commentsData
|
||||
})
|
||||
|
||||
const articleHandledBody = computed(
|
||||
() => !article.body ? '' : md2html(article.body, { sanitizer: DOMPurify.sanitize }),
|
||||
)
|
||||
|
||||
return {
|
||||
article,
|
||||
articleHandledBody,
|
||||
comments,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -7,6 +7,7 @@ const router = createRouter({
|
|||
{ path: '/', component: Home },
|
||||
{ path: '/my-feeds', component: Home },
|
||||
{ path: '/tag/:tag', component: Home },
|
||||
{ path: '/article/:slug', component: () => import('./pages/Article.vue') },
|
||||
],
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue