Merge branch 'master' into improve-request
This commit is contained in:
commit
fe45801e9d
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<router-link
|
||||
:to="to"
|
||||
v-bind="attrs"
|
||||
>
|
||||
<slot />
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import type { RouteParams } from 'vue-router'
|
||||
import type { AppRouteNames } from '../routes'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppLink',
|
||||
props: {
|
||||
name: { type: String as PropType<AppRouteNames>, required: true },
|
||||
params: { type: Object as PropType<RouteParams>, default: () => ({}) },
|
||||
},
|
||||
setup (props, { attrs }) {
|
||||
return {
|
||||
to: props,
|
||||
attrs,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
@ -7,24 +7,26 @@
|
|||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<RouterLink
|
||||
:to="`/profile/${comment.author.username}`"
|
||||
<AppLink
|
||||
name="profile"
|
||||
:params="{username: comment.author.username}"
|
||||
class="comment-author"
|
||||
>
|
||||
<img
|
||||
:src="comment.author.image"
|
||||
class="comment-author-img"
|
||||
>
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
|
||||
|
||||
|
||||
<RouterLink
|
||||
:to="`/profile/${comment.author.username}`"
|
||||
<AppLink
|
||||
name="profile"
|
||||
:params="{username: comment.author.username}"
|
||||
class="comment-author"
|
||||
>
|
||||
{{ comment.author.username }}
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
|
||||
<span class="date-posted">{{ (new Date(comment.createdAt)).toLocaleDateString() }}</span>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
<a href=""><img :src="article.author?.image"></a>
|
||||
|
||||
<div class="info">
|
||||
<RouterLink
|
||||
:to="`/profile/${article.author?.username}`"
|
||||
<AppLink
|
||||
name="profile"
|
||||
:params="{username: article.author?.username}"
|
||||
class="author"
|
||||
>
|
||||
{{ article.author?.username }}
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
|
||||
<span class="date">{{ (new Date(article.createdAt)).toLocaleDateString() }}</span>
|
||||
</div>
|
||||
|
|
@ -30,12 +31,13 @@
|
|||
|
||||
|
||||
|
||||
<RouterLink
|
||||
<AppLink
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
:to="`/editor/${article.slug}`"
|
||||
name="editor"
|
||||
:params="{slug: article.slug}"
|
||||
>
|
||||
<i class="ion-edit" /> Edit Article
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,35 @@
|
|||
<template>
|
||||
<div class="article-preview">
|
||||
<div class="article-meta">
|
||||
<RouterLink :to="`/profile/${article.author.username}`">
|
||||
<AppLink
|
||||
name="profile"
|
||||
:params="{username: article.author.username}"
|
||||
>
|
||||
<img :src="article.author.image">
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
<div class="info">
|
||||
<RouterLink
|
||||
:to="`/profile/${article.author.username}`"
|
||||
<AppLink
|
||||
name="profile"
|
||||
:params="{username: article.author.username}"
|
||||
class="author"
|
||||
>
|
||||
{{ article.author.username }}
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
<span class="date">{{ new Date(article.createdAt).toDateString() }}</span>
|
||||
</div>
|
||||
<button class="btn btn-outline-primary btn-sm pull-xs-right">
|
||||
<i class="ion-heart" /> {{ article.favoritesCount }}
|
||||
</button>
|
||||
</div>
|
||||
<RouterLink
|
||||
:to="`/article/${article.slug}`"
|
||||
<AppLink
|
||||
name="article"
|
||||
:params="{slug: article.slug}"
|
||||
class="preview-link"
|
||||
>
|
||||
<h1>{{ article.title }}</h1>
|
||||
<p>{{ article.description }}</p>
|
||||
<span>Read more...</span>
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,28 +5,32 @@
|
|||
:key="link.type"
|
||||
class="nav-item"
|
||||
>
|
||||
<RouterLink
|
||||
<AppLink
|
||||
class="nav-link"
|
||||
active-class="active"
|
||||
:to="link.href"
|
||||
:name="link.name"
|
||||
:params="link.params"
|
||||
>
|
||||
<i
|
||||
v-if="link.icon"
|
||||
:class="link.icon"
|
||||
/> {{ link.title }}
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, toRefs } from 'vue'
|
||||
import type { RouteParams } from 'vue-router'
|
||||
import type { AppRouteNames } from '../routes'
|
||||
|
||||
type ArticleNavLinkType = 'globalFeed' | 'myFeed' | 'tag' | 'author' | 'favorited'
|
||||
|
||||
interface ArticleNavLink {
|
||||
name: AppRouteNames
|
||||
params?: Partial<RouteParams>
|
||||
type: ArticleNavLinkType
|
||||
href: string
|
||||
title: string
|
||||
icon?: string
|
||||
}
|
||||
|
|
@ -44,11 +48,36 @@ export default defineComponent({
|
|||
const { useGlobalFeed, useMyFeed, useTag, useAuthor, useFavorited } = toRefs(props)
|
||||
|
||||
const allLinks = computed<ArticleNavLink[]>(() => [
|
||||
{ type: 'globalFeed', href: '/', title: 'Global Feed' },
|
||||
{ type: 'myFeed', href: '/my-feeds', title: 'Your Feed' },
|
||||
{ type: 'tag', href: `/tag/${useTag.value}`, title: useTag.value, icon: 'ion-pound' },
|
||||
{ type: 'author', href: `/profile/${useAuthor.value}`, title: 'My articles' },
|
||||
{ type: 'favorited', href: `/profile/${useFavorited.value}/favorites`, title: 'Favorited Articles' },
|
||||
{
|
||||
type: 'globalFeed',
|
||||
name: 'global-feed',
|
||||
title: 'Global Feed',
|
||||
},
|
||||
{
|
||||
type: 'myFeed',
|
||||
name: 'my-feed',
|
||||
title: 'Your Feed',
|
||||
},
|
||||
|
||||
{
|
||||
type: 'author',
|
||||
name: 'profile',
|
||||
params: { username: useAuthor.value },
|
||||
title: 'My articles',
|
||||
},
|
||||
{
|
||||
type: 'tag',
|
||||
name: 'tag',
|
||||
params: { tag: useTag.value },
|
||||
title: useTag.value,
|
||||
icon: 'ion-pound',
|
||||
},
|
||||
{
|
||||
type: 'favorited',
|
||||
name: 'profile-favorites',
|
||||
params: { username: useFavorited.value },
|
||||
title: 'Favorited Articles',
|
||||
},
|
||||
])
|
||||
|
||||
const show = computed<Record<ArticleNavLinkType, boolean>>(() => ({
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<footer>
|
||||
<div class="container">
|
||||
<RouterLink
|
||||
to="/"
|
||||
<AppLink
|
||||
name="global-feed"
|
||||
class="logo-font"
|
||||
>
|
||||
conduit
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
<span class="attribution">
|
||||
An interactive learning project from <a href="https://thinkster.io">Thinkster</a>. Code & design licensed under MIT.
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
<template>
|
||||
<nav class="navbar navbar-light">
|
||||
<div class="container">
|
||||
<RouterLink
|
||||
<AppLink
|
||||
class="navbar-brand"
|
||||
to="/"
|
||||
name="global-feed"
|
||||
>
|
||||
conduit
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
|
||||
<ul class="nav navbar-nav pull-xs-right">
|
||||
<li
|
||||
v-for="link in navLinks"
|
||||
:key="link.to"
|
||||
:key="link.name"
|
||||
class="nav-item"
|
||||
>
|
||||
<RouterLink
|
||||
<AppLink
|
||||
class="nav-link"
|
||||
active-class="active"
|
||||
:to="link.to"
|
||||
:name="link.name"
|
||||
:params="link.params"
|
||||
>
|
||||
<i
|
||||
v-if="link.icon"
|
||||
:class="link.icon"
|
||||
/> {{ link.title }}
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
</li>
|
||||
<!-- TODO: remove logout link -->
|
||||
<li
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { computed, Ref } from 'vue'
|
||||
import type { RouteParams } from 'vue-router'
|
||||
import type { AppRouteNames } from '../routes'
|
||||
|
||||
interface UseLinksProps {
|
||||
user: Ref<User | null>
|
||||
}
|
||||
|
||||
interface NavLink {
|
||||
to: string
|
||||
name: AppRouteNames
|
||||
params?: Partial<RouteParams>
|
||||
title: string
|
||||
icon?: string
|
||||
display: 'all' | 'anonym' | 'authorized'
|
||||
|
|
@ -16,12 +19,39 @@ export function useNavigationLinks ({ user }: UseLinksProps) {
|
|||
const displayStatus = computed(() => username.value ? 'authorized' : 'anonym')
|
||||
|
||||
const allNavLinks: NavLink[] = [
|
||||
{ to: '/', title: 'Home', display: 'all' },
|
||||
{ to: '/login', title: 'Sign in', display: 'anonym' },
|
||||
{ to: '/register', title: 'Sign up', display: 'anonym' },
|
||||
{ to: '/editor', title: 'New Post', display: 'authorized', icon: 'ion-compose' },
|
||||
{ to: '/settings', title: 'Settings', display: 'authorized', icon: 'ion-gear-a' },
|
||||
{ to: `/profile/${username.value}`, title: username.value || '', display: 'authorized' },
|
||||
{
|
||||
name: 'global-feed',
|
||||
title: 'Home',
|
||||
display: 'all',
|
||||
},
|
||||
{
|
||||
name: 'login',
|
||||
title: 'Sign in',
|
||||
display: 'anonym',
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
title: 'Sign up',
|
||||
display: 'anonym',
|
||||
},
|
||||
{
|
||||
name: 'editor',
|
||||
title: 'New Post',
|
||||
display: 'authorized',
|
||||
icon: 'ion-compose',
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
title: 'Settings',
|
||||
display: 'authorized',
|
||||
icon: 'ion-gear-a',
|
||||
},
|
||||
{
|
||||
name: 'profile',
|
||||
params: { username: username.value },
|
||||
title: username.value || '',
|
||||
display: 'authorized',
|
||||
},
|
||||
]
|
||||
|
||||
const navLinks = computed(() => allNavLinks.filter(
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
<p>Popular Tags</p>
|
||||
|
||||
<div class="tag-list">
|
||||
<RouterLink
|
||||
<AppLink
|
||||
v-for="tag in tags"
|
||||
:key="tag"
|
||||
:to="`/tag/${tag}`"
|
||||
name="tag"
|
||||
:params="{tag}"
|
||||
class="tag-pill tag-default"
|
||||
>
|
||||
{{ tag }}
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
13
src/main.ts
13
src/main.ts
|
|
@ -2,14 +2,17 @@ import router from './routes'
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import store from './store'
|
||||
|
||||
import registerGlobalComponents from './plugins/global-components'
|
||||
import { request } from './services'
|
||||
import parseStorageGet from './utils/parse-storage-get'
|
||||
|
||||
const token = parseStorageGet('user')?.token
|
||||
request.setAuthorizationHeader(token)
|
||||
|
||||
createApp(App)
|
||||
.use(router)
|
||||
.use(store)
|
||||
.mount('#app')
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
|
||||
registerGlobalComponents(app)
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
Sign in
|
||||
</h1>
|
||||
<p class="text-xs-center">
|
||||
<RouterLink to="/register">
|
||||
<AppLink name="register">
|
||||
Need an account?
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
</p>
|
||||
|
||||
<ul class="error-messages">
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
Sign up
|
||||
</h1>
|
||||
<p class="text-xs-center">
|
||||
<RouterLink to="/login">
|
||||
<AppLink name="login">
|
||||
Have an account?
|
||||
</RouterLink>
|
||||
</AppLink>
|
||||
</p>
|
||||
|
||||
<ul class="error-messages">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import type { App } from 'vue'
|
||||
|
||||
import AppLink from '../components/AppLink.vue'
|
||||
|
||||
export default function registerGlobalComponents (app: App) {
|
||||
app.component('AppLink', AppLink)
|
||||
}
|
||||
|
|
@ -1,17 +1,70 @@
|
|||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import Home from './pages/Home.vue'
|
||||
|
||||
export type AppRouteNames = 'global-feed'
|
||||
|'my-feed'
|
||||
|'tag'
|
||||
|'article'
|
||||
|'login'
|
||||
|'register'
|
||||
|'profile'
|
||||
|'profile-favorites'
|
||||
|'editor'
|
||||
|'settings'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: Home },
|
||||
{ path: '/my-feeds', component: Home },
|
||||
{ path: '/tag/:tag', component: Home },
|
||||
{ path: '/article/:slug', component: () => import('./pages/Article.vue') },
|
||||
{ path: '/login', component: () => import('./pages/Login.vue') },
|
||||
{ path: '/register', component: () => import('./pages/Register.vue') },
|
||||
{ path: '/profile/:username', component: () => import('./pages/Profile.vue') },
|
||||
{ path: '/profile/:username/favorites', component: () => import('./pages/Profile.vue') },
|
||||
{
|
||||
name: 'global-feed',
|
||||
path: '/',
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
name: 'my-feed',
|
||||
path: '/my-feeds',
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
name: 'tag',
|
||||
path: '/tag/:tag',
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
name: 'article',
|
||||
path: '/article/:slug',
|
||||
component: () => import('./pages/Article.vue'),
|
||||
},
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login',
|
||||
component: () => import('./pages/Login.vue'),
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
path: '/register',
|
||||
component: () => import('./pages/Register.vue'),
|
||||
},
|
||||
{
|
||||
name: 'profile',
|
||||
path: '/profile/:username',
|
||||
component: () => import('./pages/Profile.vue'),
|
||||
},
|
||||
{
|
||||
name: 'profile-favorites',
|
||||
path: '/profile/:username/favorites',
|
||||
component: () => import('./pages/Profile.vue'),
|
||||
},
|
||||
{
|
||||
name: 'editor',
|
||||
path: '/editor',
|
||||
redirect: '/',
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
path: '/settings',
|
||||
redirect: '/',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue