feat : [FE] Article Update ADD.

This commit is contained in:
minseokkang
2022-12-05 19:01:06 +09:00
parent 74451508e9
commit 427eae6efd
6 changed files with 218 additions and 49 deletions

View File

@@ -74,6 +74,16 @@ const createArticle = async (article: object | undefined): Promise<AxiosResponse
}) })
} }
const updateArticle = async (article: object | undefined, slug: string): Promise<AxiosResponse> => {
let currentToken = localStorage.getItem("token");
return await axiosService.put('/api/articles/' + slug, { article },{
headers :{
Authorization : "TOKEN " + currentToken,
"Content-Type": `application/json`,
}
})
}
const listArticles = async (): Promise<AxiosResponse> => { const listArticles = async (): Promise<AxiosResponse> => {
let currentToken = localStorage.getItem("token"); let currentToken = localStorage.getItem("token");
if(currentToken == null){ if(currentToken == null){
@@ -100,6 +110,19 @@ const listArticlesByUsername = async (author: string): Promise<AxiosResponse> =>
} }
} }
const listArticlesByFavorite = async (author: string): Promise<AxiosResponse> => {
let currentToken = localStorage.getItem("token");
if(currentToken == null){
return await axiosService.get('/api/articles?favorited=' + author);
}else{
return await axiosService.get('/api/articles?favorited=' + author,{
headers:{
Authorization: "TOKEN " + currentToken,
}
})
}
}
const feedArticle = async (): Promise<AxiosResponse> => { const feedArticle = async (): Promise<AxiosResponse> => {
let currentToken = localStorage.getItem("token"); let currentToken = localStorage.getItem("token");
return await axiosService.get('/api/articles/feed?limit=1000&offset=0',{ return await axiosService.get('/api/articles/feed?limit=1000&offset=0',{
@@ -182,5 +205,6 @@ export { signUp, signIn,
unfollowUser, getArticle, unfollowUser, getArticle,
addCommentToArticle, getCommentsFromArticle, addCommentToArticle, getCommentsFromArticle,
favoriteArticle, unFavoriteArticle, favoriteArticle, unFavoriteArticle,
listArticlesByFavorite, updateArticle,
getTags getTags
} }

View File

@@ -1,5 +1,5 @@
import { ref, Ref } from "@vue/reactivity"; import { ref, Ref } from "@vue/reactivity";
import { listArticles, feedArticle } from "@/api/index"; import {listArticles, feedArticle, listArticlesByUsername, listArticlesByFavorite} from "@/api/index";
import { usePagination } from "@/ts/usePagination"; import { usePagination } from "@/ts/usePagination";
@@ -63,11 +63,45 @@ export function usePaginationApi(
} }
}; };
const loadMyArticles = async (author: string) => {
listsAreLoading.value = true;
isEmpty.value = false;
try{
const { data } = await listArticlesByUsername(author);
articleLists.value = data.articles;
if(data.articlesCount == 0){
isEmpty.value = true;
}
}catch (err){
console.log(err);
}finally {
listsAreLoading.value = false;
}
}
const loadFavoriteArticles = async (author: string) => {
listsAreLoading.value = true;
isEmpty.value = false;
try{
const { data } = await listArticlesByFavorite(author);
articleLists.value = data.articles;
if(data.articlesCount == 0){
isEmpty.value = true;
}
}catch (err){
console.log(err);
}finally {
listsAreLoading.value = false;
}
}
return { return {
articleLists: paginatedArray, articleLists: paginatedArray,
loadLists, loadLists,
feedLists, feedLists,
loadMyArticles,
loadFavoriteArticles,
listsAreLoading, listsAreLoading,
isEmpty, isEmpty,
numberOfPages numberOfPages

View File

@@ -33,6 +33,12 @@ const routes = [
name: "ArticleEditor", name: "ArticleEditor",
component: () => import(/* webpackChunkName "inputTag" */ '@/views/TheArticle.vue') component: () => import(/* webpackChunkName "inputTag" */ '@/views/TheArticle.vue')
}, },
{
path: "/editor/@:slug",
name: "ArticleUpdateEditor",
component: () => import(/* webpackChunkName "inputTag" */ '@/views/ArticleUpdate.vue'),
props: true
},
{ {
path: "/@:username", path: "/@:username",
name: "Profile", name: "Profile",

View File

@@ -0,0 +1,74 @@
<template>
<div class="editor-page">
<div class="container page">
<div class="row">
<div class="col-md-10 offset-md-1 col-xs-12">
<form>
<fieldset>
<fieldset class="form-group">
<input type="text" class="form-control form-control-lg" placeholder="Article Title" v-model="article.title">
</fieldset>
<fieldset class="form-group">
<input type="text" class="form-control" placeholder="What's this article about?" v-model="article.description">
</fieldset>
<fieldset class="form-group">
<textarea class="form-control" rows="8"
placeholder="Write your article (in markdown)" v-model="article.body"></textarea>
</fieldset>
<fieldset class="form-group">
<input type="text" class="form-control" placeholder="Enter tags using ',' Add tag">
<div class="tag-list"></div>
</fieldset>
<button @click="updateContent" class="btn btn-lg pull-xs-right btn-primary" type="button">
Publish Article
</button>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { reactive } from "vue";
import { updateArticle } from "@/api/index.js";
import router from "@/router";
export default {
name: "ArticleUpdate",
props:{
slug: String,
},
setup(){
const article = reactive({
title: "",
description: "",
body: "",
})
const getSlug = (title:string) => {
return title.replace(' ','-');
}
const updateContent = async () => {
const slug = getSlug(article.title);
try{
await updateArticle(article, slug);
await router.push({
name:"ArticleDetail",
params: {slug}
})
}catch (error: any){
alert(error);
}
}
return { article, updateContent }
}
}
</script>
<style scoped>
</style>

View File

@@ -12,7 +12,15 @@
<a href="javascript:void(0)" class="author" @click="viewProfile">{{ articleDetail.article.author.username }}</a> <a href="javascript:void(0)" class="author" @click="viewProfile">{{ articleDetail.article.author.username }}</a>
<span class="date">{{convertDate(articleDetail.article.createdAt)}}</span> <span class="date">{{convertDate(articleDetail.article.createdAt)}}</span>
</div> </div>
<button class="btn btn-sm btn-outline-secondary" @click="followUpdate(articleDetail.article.author.following)"> <button v-if= "isMe" class="btn btn-sm btn-outline-secondary" @click="articleUpdate()">
<i class="ion-edit"></i>
Edit Article
</button>
<button v-if= "isMe" class="btn btn-outline-danger btn-sm" @click="followUpdate(articleDetail.article.author.following)">
<i class="ion-trash-a"></i>
Delete Article
</button>
<button v-if= "!isMe" class="btn btn-sm btn-outline-secondary" @click="followUpdate(articleDetail.article.author.following)">
<div v-if="articleDetail.article.author.following"> <div v-if="articleDetail.article.author.following">
<i class="ion-minus-round"></i> <i class="ion-minus-round"></i>
unFollow {{articleDetail.article.author.username}} unFollow {{articleDetail.article.author.username}}
@@ -23,7 +31,7 @@
</div> </div>
</button> </button>
&nbsp;&nbsp; &nbsp;&nbsp;
<button class="btn btn-sm btn-outline-primary" @click="favoriteUpdate(articleDetail.article.favorited)"> <button v-if="!isMe" class="btn btn-sm btn-outline-primary" @click="favoriteUpdate(articleDetail.article.favorited)">
<div v-if="articleDetail.article.favorited"> <div v-if="articleDetail.article.favorited">
<i class="ion-heart"></i> <i class="ion-heart"></i>
unFavorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>) unFavorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>)
@@ -45,7 +53,6 @@
{{articleDetail.article.body}} {{articleDetail.article.body}}
</div> </div>
</div> </div>
<hr/> <hr/>
<div class="article-actions"> <div class="article-actions">
@@ -55,7 +62,15 @@
<a href="javascript:void(0)" class="author" @click="viewProfile">{{ articleDetail.article.author.username }}</a> <a href="javascript:void(0)" class="author" @click="viewProfile">{{ articleDetail.article.author.username }}</a>
<span class="date">{{convertDate(articleDetail.article.createdAt)}}</span> <span class="date">{{convertDate(articleDetail.article.createdAt)}}</span>
</div> </div>
<button class="btn btn-sm btn-outline-secondary" @click="followUpdate(articleDetail.article.author.following)"> <button v-if= "isMe" class="btn btn-sm btn-outline-secondary" @click="followUpdate(articleDetail.article.author.following)">
<i class="ion-edit"></i>
Edit Article
</button>
<button v-if= "isMe" class="btn btn-outline-danger btn-sm" @click="followUpdate(articleDetail.article.author.following)">
<i class="ion-trash-a"></i>
Delete Article
</button>
<button v-if= "!isMe" class="btn btn-sm btn-outline-secondary" @click="followUpdate(articleDetail.article.author.following)">
<div v-if="articleDetail.article.author.following"> <div v-if="articleDetail.article.author.following">
<i class="ion-minus-round"></i> <i class="ion-minus-round"></i>
unFollow {{articleDetail.article.author.username}} unFollow {{articleDetail.article.author.username}}
@@ -65,13 +80,13 @@
Follow {{articleDetail.article.author.username}} Follow {{articleDetail.article.author.username}}
</div> </div>
</button> </button>
&nbsp; &nbsp;&nbsp;
<button class="btn btn-sm btn-outline-primary" @click="favoriteUpdate(articleDetail.article.favorited)"> <button v-if="!isMe" class="btn btn-sm btn-outline-primary" @click="favoriteUpdate(articleDetail.article.favorited)">
<div v-if="articleDetail.article.favorited"> <div v-if="articleDetail.article.favorited">
<i class="ion-heart"></i> <i class="ion-heart"></i>
unFavorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>) unFavorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>)
</div> </div>
&nbsp;<div v-else> <div v-else>
<i class="ion-heart"></i> <i class="ion-heart"></i>
Favorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>) Favorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>)
</div> </div>
@@ -109,7 +124,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { onMounted, defineComponent, reactive } from "vue"; import {onMounted, defineComponent, reactive, ref} from "vue";
import commentList from "@/components/commentList.vue"; import commentList from "@/components/commentList.vue";
import router from "@/router"; import router from "@/router";
import { useStore } from "vuex"; import { useStore } from "vuex";
@@ -134,6 +149,8 @@ export default defineComponent({
setup(props){ setup(props){
const store = useStore(); const store = useStore();
const token = store.state.token; const token = store.state.token;
const username = store.state.username;
const isMe = ref(false);
const comment = reactive({ const comment = reactive({
body: "" body: ""
@@ -169,6 +186,13 @@ export default defineComponent({
params: {username: articleDetail.article.author.username}}) params: {username: articleDetail.article.author.username}})
} }
const articleUpdate = async () => {
await router.push({
name: 'ArticleEditor',
params: {slug: articleDetail.article.slug}
})
}
const followUpdate = async (followState: boolean) => { const followUpdate = async (followState: boolean) => {
if(token == ''){ if(token == ''){
await router.push({name:"Login"}); await router.push({name:"Login"});
@@ -221,6 +245,9 @@ export default defineComponent({
try{ try{
const { data } = await getArticle(props.slug); const { data } = await getArticle(props.slug);
articleDetail.article = data.article; articleDetail.article = data.article;
if(articleDetail.article.author.username == username){
isMe.value = true;
}
}catch (error: any){ }catch (error: any){
alert(error); alert(error);
} }
@@ -233,7 +260,7 @@ export default defineComponent({
} }
}) })
return { articleDetail, comment, getCommentList, convertDate, viewProfile, followUpdate, favoriteUpdate, sendComment } return { isMe, articleDetail, comment, getCommentList, convertDate, viewProfile, articleUpdate, followUpdate, favoriteUpdate, sendComment }
} }
}) })
</script> </script>

View File

@@ -1,11 +1,8 @@
<template> <template>
<div class="profile-page"> <div class="profile-page">
<div class="user-info"> <div class="user-info">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-10 offset-md-1"> <div class="col-xs-12 col-md-10 offset-md-1">
<img :src="profile.image" class="user-img"/> <img :src="profile.image" class="user-img"/>
<h4>{{ profile.username }}</h4> <h4>{{ profile.username }}</h4>
@@ -29,7 +26,6 @@
</div> </div>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -41,19 +37,29 @@
<div class="articles-toggle"> <div class="articles-toggle">
<ul class="nav nav-pills outline-active"> <ul class="nav nav-pills outline-active">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="javascript">My Articles</a> <a href="javascript:(0)" class="nav-link"
@click="myArticleSelect"
:class="{ active : myArticleActive}">My Articles</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="">Favorited Articles</a> <a href="javascript:(0)" class="nav-link"
@click="favoriteArticleSelect"
:class="{ active : favoriteArticleActive}">Favorited Articles</a>
</li> </li>
</ul> </ul>
<div v-if="listsAreLoading">
Loading articles...
</div>
<div v-if="isEmpty">
No articles are here... yet.
</div>
</div> </div>
<article-my v-for="(article,index) in articleLists"
<article-my v-for="(art,index) in articles.article" :key="article.slug"
:article="art"> :article="article">
</article-my> </article-my>
<pagination-component v-model="currentPage" :numberOfPages="numberOfPages"></pagination-component>
</div> </div>
</div> </div>
</div> </div>
@@ -64,23 +70,23 @@
import { onMounted, reactive, ref } from "vue"; import { onMounted, reactive, ref } from "vue";
import { useStore } from "vuex"; import { useStore } from "vuex";
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import pagination from "@/components/PaginationComponent.vue";
import { usePaginationApi } from "@/api/usePaginationAPI"
import router from "@/router"; import router from "@/router";
import { followUser, getProfile, listArticlesByUsername, unfollowUser } from "@/api"; import { followUser, getProfile, unfollowUser } from "@/api";
import ArticleMy from "@/components/ArticleMy.vue"; import ArticleMy from "@/components/ArticleMy.vue";
export default defineComponent({ export default defineComponent({
name: "TheProfile.vue", name: "TheProfile.vue",
components: { components: {
"article-my": ArticleMy, "article-my": ArticleMy,
'pagination-component': pagination,
}, },
props:{ props:{
username: String, username: String,
}, },
setup(props){ setup(props){
const url = import.meta.env.VITE_BASE_URL;
const store = useStore(); const store = useStore();
const isMe = ref(false); const isMe = ref(false);
const profile = reactive({ const profile = reactive({
image: "", image: "",
@@ -88,26 +94,14 @@ export default defineComponent({
bio: "", bio: "",
following: false, following: false,
}) })
const myArticleActive = ref(true);
const favoriteArticleActive = ref(false);
const articles = reactive({
article: reactive([{ const currentPage = ref(1);
slug: "", const rowsPerPage = ref(5);
title: "",
description: "", const { articleLists, listsAreLoading, isEmpty,loadMyArticles, loadFavoriteArticles, numberOfPages } = usePaginationApi(currentPage, rowsPerPage);
body: "",
tagList: new Array(),
createdAt: "",
updatedAt: "",
favorited: false,
favoritesCount: 0,
author: {
username: "",
bio: "",
image: "",
following: false
}
}])
})
const setProfile = async ( data: any ) => { const setProfile = async ( data: any ) => {
profile.image = data.image; profile.image = data.image;
@@ -138,10 +132,22 @@ export default defineComponent({
} }
} }
const myArticleSelect = async () => {
myArticleActive.value=true;
favoriteArticleActive.value=false;
await loadMyArticles(profile.username);
}
const favoriteArticleSelect = async () => {
myArticleActive.value=false;
favoriteArticleActive.value=true;
await loadFavoriteArticles(profile.username);
}
onMounted(async () =>{ onMounted(async () =>{
try { try {
const { data } = await getProfile(props.username) const { data } = await getProfile(props.username)
await setProfile(data.profile); await setProfile(data.profile);
if(data.profile.username.localeCompare(store.state.username) == 0){ if(data.profile.username.localeCompare(store.state.username) == 0){
isMe.value = true; isMe.value = true;
@@ -153,15 +159,13 @@ export default defineComponent({
if(code == "USER_NOT_FOUND") if(code == "USER_NOT_FOUND")
await router.push({name:"home"}); await router.push({name:"home"});
} }
try{ try{
const { data } = await listArticlesByUsername(profile.username); await loadMyArticles(profile.username);
articles.article = data.articles;
}catch (error: any){ }catch (error: any){
alert(error); alert(error);
} }
}) })
return { url, isMe, profile, articles, stateUpdate } return { isMe, listsAreLoading, isEmpty, myArticleActive, favoriteArticleActive, profile, articleLists, currentPage, rowsPerPage, stateUpdate, myArticleSelect, favoriteArticleSelect, numberOfPages }
} }
}) })
</script> </script>