feat : [FE] Article Update ADD.
This commit is contained in:
@@ -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> => {
|
||||
let currentToken = localStorage.getItem("token");
|
||||
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> => {
|
||||
let currentToken = localStorage.getItem("token");
|
||||
return await axiosService.get('/api/articles/feed?limit=1000&offset=0',{
|
||||
@@ -182,5 +205,6 @@ export { signUp, signIn,
|
||||
unfollowUser, getArticle,
|
||||
addCommentToArticle, getCommentsFromArticle,
|
||||
favoriteArticle, unFavoriteArticle,
|
||||
listArticlesByFavorite, updateArticle,
|
||||
getTags
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
@@ -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 {
|
||||
articleLists: paginatedArray,
|
||||
loadLists,
|
||||
feedLists,
|
||||
loadMyArticles,
|
||||
loadFavoriteArticles,
|
||||
listsAreLoading,
|
||||
isEmpty,
|
||||
numberOfPages
|
||||
|
||||
@@ -33,6 +33,12 @@ const routes = [
|
||||
name: "ArticleEditor",
|
||||
component: () => import(/* webpackChunkName "inputTag" */ '@/views/TheArticle.vue')
|
||||
},
|
||||
{
|
||||
path: "/editor/@:slug",
|
||||
name: "ArticleUpdateEditor",
|
||||
component: () => import(/* webpackChunkName "inputTag" */ '@/views/ArticleUpdate.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: "/@:username",
|
||||
name: "Profile",
|
||||
|
||||
74
src/vite-frontend/src/views/ArticleUpdate.vue
Normal file
74
src/vite-frontend/src/views/ArticleUpdate.vue
Normal 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>
|
||||
@@ -12,7 +12,15 @@
|
||||
<a href="javascript:void(0)" class="author" @click="viewProfile">{{ articleDetail.article.author.username }}</a>
|
||||
<span class="date">{{convertDate(articleDetail.article.createdAt)}}</span>
|
||||
</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">
|
||||
<i class="ion-minus-round"></i>
|
||||
unFollow {{articleDetail.article.author.username}}
|
||||
@@ -23,7 +31,7 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<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">
|
||||
<i class="ion-heart"></i>
|
||||
unFavorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>)
|
||||
@@ -45,7 +53,6 @@
|
||||
{{articleDetail.article.body}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="article-actions">
|
||||
@@ -55,7 +62,15 @@
|
||||
<a href="javascript:void(0)" class="author" @click="viewProfile">{{ articleDetail.article.author.username }}</a>
|
||||
<span class="date">{{convertDate(articleDetail.article.createdAt)}}</span>
|
||||
</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">
|
||||
<i class="ion-minus-round"></i>
|
||||
unFollow {{articleDetail.article.author.username}}
|
||||
@@ -65,13 +80,13 @@
|
||||
Follow {{articleDetail.article.author.username}}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<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">
|
||||
<i class="ion-heart"></i>
|
||||
unFavorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>)
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-else>
|
||||
<i class="ion-heart"></i>
|
||||
Favorite Article (<span class="counter">{{articleDetail.article.favoritesCount}}</span>)
|
||||
</div>
|
||||
@@ -109,7 +124,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMounted, defineComponent, reactive } from "vue";
|
||||
import {onMounted, defineComponent, reactive, ref} from "vue";
|
||||
import commentList from "@/components/commentList.vue";
|
||||
import router from "@/router";
|
||||
import { useStore } from "vuex";
|
||||
@@ -134,6 +149,8 @@ export default defineComponent({
|
||||
setup(props){
|
||||
const store = useStore();
|
||||
const token = store.state.token;
|
||||
const username = store.state.username;
|
||||
const isMe = ref(false);
|
||||
|
||||
const comment = reactive({
|
||||
body: ""
|
||||
@@ -169,6 +186,13 @@ export default defineComponent({
|
||||
params: {username: articleDetail.article.author.username}})
|
||||
}
|
||||
|
||||
const articleUpdate = async () => {
|
||||
await router.push({
|
||||
name: 'ArticleEditor',
|
||||
params: {slug: articleDetail.article.slug}
|
||||
})
|
||||
}
|
||||
|
||||
const followUpdate = async (followState: boolean) => {
|
||||
if(token == ''){
|
||||
await router.push({name:"Login"});
|
||||
@@ -221,6 +245,9 @@ export default defineComponent({
|
||||
try{
|
||||
const { data } = await getArticle(props.slug);
|
||||
articleDetail.article = data.article;
|
||||
if(articleDetail.article.author.username == username){
|
||||
isMe.value = true;
|
||||
}
|
||||
}catch (error: any){
|
||||
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>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<template>
|
||||
|
||||
<div class="profile-page">
|
||||
|
||||
<div class="user-info">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-12 col-md-10 offset-md-1">
|
||||
<img :src="profile.image" class="user-img"/>
|
||||
<h4>{{ profile.username }}</h4>
|
||||
@@ -29,7 +26,6 @@
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,19 +37,29 @@
|
||||
<div class="articles-toggle">
|
||||
<ul class="nav nav-pills outline-active">
|
||||
<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 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>
|
||||
</ul>
|
||||
<div v-if="listsAreLoading">
|
||||
Loading articles...
|
||||
</div>
|
||||
<div v-if="isEmpty">
|
||||
No articles are here... yet.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article-my v-for="(art,index) in articles.article"
|
||||
:article="art">
|
||||
</article-my>
|
||||
<article-my v-for="(article,index) in articleLists"
|
||||
:key="article.slug"
|
||||
:article="article">
|
||||
</article-my>
|
||||
<pagination-component v-model="currentPage" :numberOfPages="numberOfPages"></pagination-component>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,23 +70,23 @@
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { defineComponent } from 'vue';
|
||||
import pagination from "@/components/PaginationComponent.vue";
|
||||
import { usePaginationApi } from "@/api/usePaginationAPI"
|
||||
import router from "@/router";
|
||||
import { followUser, getProfile, listArticlesByUsername, unfollowUser } from "@/api";
|
||||
import { followUser, getProfile, unfollowUser } from "@/api";
|
||||
import ArticleMy from "@/components/ArticleMy.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TheProfile.vue",
|
||||
components: {
|
||||
"article-my": ArticleMy,
|
||||
'pagination-component': pagination,
|
||||
},
|
||||
props:{
|
||||
username: String,
|
||||
},
|
||||
setup(props){
|
||||
|
||||
const url = import.meta.env.VITE_BASE_URL;
|
||||
const store = useStore();
|
||||
|
||||
const isMe = ref(false);
|
||||
const profile = reactive({
|
||||
image: "",
|
||||
@@ -88,26 +94,14 @@ export default defineComponent({
|
||||
bio: "",
|
||||
following: false,
|
||||
})
|
||||
const myArticleActive = ref(true);
|
||||
const favoriteArticleActive = ref(false);
|
||||
|
||||
const articles = reactive({
|
||||
article: reactive([{
|
||||
slug: "",
|
||||
title: "",
|
||||
description: "",
|
||||
body: "",
|
||||
tagList: new Array(),
|
||||
createdAt: "",
|
||||
updatedAt: "",
|
||||
favorited: false,
|
||||
favoritesCount: 0,
|
||||
author: {
|
||||
username: "",
|
||||
bio: "",
|
||||
image: "",
|
||||
following: false
|
||||
}
|
||||
}])
|
||||
})
|
||||
|
||||
const currentPage = ref(1);
|
||||
const rowsPerPage = ref(5);
|
||||
|
||||
const { articleLists, listsAreLoading, isEmpty,loadMyArticles, loadFavoriteArticles, numberOfPages } = usePaginationApi(currentPage, rowsPerPage);
|
||||
|
||||
const setProfile = async ( data: any ) => {
|
||||
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 () =>{
|
||||
try {
|
||||
const { data } = await getProfile(props.username)
|
||||
const { data } = await getProfile(props.username)
|
||||
await setProfile(data.profile);
|
||||
if(data.profile.username.localeCompare(store.state.username) == 0){
|
||||
isMe.value = true;
|
||||
@@ -153,15 +159,13 @@ export default defineComponent({
|
||||
if(code == "USER_NOT_FOUND")
|
||||
await router.push({name:"home"});
|
||||
}
|
||||
|
||||
try{
|
||||
const { data } = await listArticlesByUsername(profile.username);
|
||||
articles.article = data.articles;
|
||||
await loadMyArticles(profile.username);
|
||||
}catch (error: any){
|
||||
alert(error);
|
||||
}
|
||||
})
|
||||
return { url, isMe, profile, articles, stateUpdate }
|
||||
return { isMe, listsAreLoading, isEmpty, myArticleActive, favoriteArticleActive, profile, articleLists, currentPage, rowsPerPage, stateUpdate, myArticleSelect, favoriteArticleSelect, numberOfPages }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user