refactor: implement script setup suger for components
This commit is contained in:
parent
2cfb9a7821
commit
c0c983dba0
18
.eslintrc
18
.eslintrc
|
|
@ -5,24 +5,20 @@
|
|||
"parser": "@typescript-eslint/parser",
|
||||
"project": "./tsconfig.json",
|
||||
"sourceType": "module",
|
||||
"extraFileExtensions": [
|
||||
".vue",
|
||||
".d.ts"
|
||||
]
|
||||
"extraFileExtensions": [".vue", ".d.ts"]
|
||||
},
|
||||
"extends": [
|
||||
"standard",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"standard-with-typescript",
|
||||
"plugin:vue/vue3-recommended"
|
||||
"plugin:vue/vue3-recommended",
|
||||
"standard-with-typescript"
|
||||
],
|
||||
"rules": {
|
||||
"no-undef": "off",
|
||||
"comma-dangle": [
|
||||
"warn",
|
||||
"always-multiline"
|
||||
],
|
||||
"@typescript-eslint/promise-function-async": "off"
|
||||
"no-unused-vars": "off",
|
||||
"comma-dangle": ["warn", "always-multiline"],
|
||||
"@typescript-eslint/promise-function-async": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ yarn build
|
|||
- [x] Unit test ([Vue Test Utils](https://github.com/vuejs/vue-test-utils-next))
|
||||
- [ ] Unit test ([Vue Testing Library](https://testing-library.com/docs/vue-testing-library/intro)) (Temporarily unavailable)
|
||||
- [x] E2E test ([Cypress](https://docs.cypress.io))
|
||||
- [ ] [SFC Script Setup](https://github.com/vuejs/rfcs/blob/sfc-improvements/active-rfcs/0000-sfc-script-setup.md) (Unstable)
|
||||
- [x] [SFC Script Setup](https://github.com/vuejs/rfcs/blob/sfc-improvements/active-rfcs/0000-sfc-script-setup.md) (Experimental)
|
||||
- [x] Vetur Tools: [VTI](https://github.com/mutoe/vue3-realworld-example-app/pull/28) and [optionally IDE hints](https://github.com/mutoe/vue3-realworld-example-app/commit/8367f89a99c467d181d9c7f4144deb05cec55210#commitcomment-43957089)
|
||||
|
||||
# Contributors
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
{
|
||||
"baseUrl": "https://mutoe.github.io/vue3-realworld-example-app",
|
||||
"experimentalNetworkStubbing": true,
|
||||
"chromeWebSecurity": false
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue3-realworld-example-app",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
"@harlem/core": "^1.1.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dompurify": "^2.2.6",
|
||||
"marked": "^2.0.0",
|
||||
"vue": "^3.0.2",
|
||||
"marked": "^1.2.9",
|
||||
"vue": "^3.0.6",
|
||||
"vue-router": "^4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
"@testing-library/vue": "^6.3.4",
|
||||
"@types/dompurify": "^2.2.1",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/marked": "^1.2.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.2",
|
||||
"@typescript-eslint/parser": "^4.15.2",
|
||||
"@vitejs/plugin-vue": "^1.1.4",
|
||||
|
|
@ -39,14 +40,13 @@
|
|||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"eslint-plugin-vue": "^7.6.0",
|
||||
"husky": "^5.1.1",
|
||||
"jest": "^26.6.3",
|
||||
"jsdom": "^16.4.0",
|
||||
"lint-staged": "^10.5.4",
|
||||
"ts-jest": "^26.5.1",
|
||||
"typescript": "^4.1.5",
|
||||
"typescript": "~4.1.5",
|
||||
"vite": "^2.0.2",
|
||||
"vti": "^0.0.24",
|
||||
"vue-jest": "^5.0.0-alpha.8"
|
||||
|
|
|
|||
11
src/App.vue
11
src/App.vue
|
|
@ -4,16 +4,7 @@
|
|||
<AppFooter />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import AppFooter from './components/AppFooter.vue'
|
||||
import AppNavigation from './components/AppNavigation.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
AppNavigation,
|
||||
AppFooter,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -21,11 +21,3 @@
|
|||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppFooter',
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,19 @@
|
|||
<template>
|
||||
<router-link
|
||||
:to="to"
|
||||
v-bind="attrs"
|
||||
>
|
||||
<router-link :to="props" v-bind="attrs">
|
||||
<slot />
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import type { RouteParams } from 'vue-router'
|
||||
<script lang="ts" setup>
|
||||
import type { AppRouteNames } from '../router'
|
||||
import type { RouteParams } from 'vue-router'
|
||||
|
||||
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,
|
||||
}
|
||||
},
|
||||
})
|
||||
import { defineProps, useContext } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
name: AppRouteNames
|
||||
params?: RouteParams
|
||||
}>()
|
||||
|
||||
const { attrs } = useContext()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,17 @@
|
|||
<template>
|
||||
<nav class="navbar navbar-light">
|
||||
<div class="container">
|
||||
<AppLink
|
||||
class="navbar-brand"
|
||||
name="global-feed"
|
||||
>
|
||||
conduit
|
||||
</AppLink>
|
||||
<AppLink class="navbar-brand" name="global-feed"> conduit </AppLink>
|
||||
|
||||
<ul class="nav navbar-nav pull-xs-right">
|
||||
<li
|
||||
v-for="link in navLinks"
|
||||
:key="link.name"
|
||||
class="nav-item"
|
||||
>
|
||||
<li v-for="link in navLinks" :key="link.name" class="nav-item">
|
||||
<AppLink
|
||||
class="nav-link"
|
||||
active-class="active"
|
||||
:name="link.name"
|
||||
:params="link.params"
|
||||
>
|
||||
<i
|
||||
v-if="link.icon"
|
||||
:class="link.icon"
|
||||
/> {{ link.title }}
|
||||
<i v-if="link.icon" :class="link.icon" /> {{ link.title }}
|
||||
</AppLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -31,12 +19,11 @@
|
|||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import type { RouteParams } from 'vue-router'
|
||||
|
||||
import type { AppRouteNames } from '../router'
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { user } from '../store/user'
|
||||
|
||||
interface NavLink {
|
||||
|
|
@ -47,9 +34,6 @@ interface NavLink {
|
|||
display: 'all' | 'anonym' | 'authorized'
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppNavigation',
|
||||
setup () {
|
||||
const username = computed(() => user.value?.username)
|
||||
const displayStatus = computed(() => username.value ? 'authorized' : 'anonym')
|
||||
|
||||
|
|
@ -92,10 +76,4 @@ export default defineComponent({
|
|||
const navLinks = computed(() => allNavLinks.value.filter(
|
||||
l => l.display === displayStatus.value || l.display === 'all',
|
||||
))
|
||||
|
||||
return {
|
||||
navLinks,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,31 +13,18 @@
|
|||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineEmit, defineProps, toRefs } from 'vue'
|
||||
import { limit } from '../services'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppPagination',
|
||||
props: {
|
||||
page: { type: Number, required: true },
|
||||
count: { type: Number, required: true },
|
||||
},
|
||||
emits: {
|
||||
'page-change': (index: number) => typeof index === 'number',
|
||||
},
|
||||
setup (props, { emit }) {
|
||||
const props = defineProps<{
|
||||
page: number
|
||||
count: number
|
||||
}>()
|
||||
const emit = defineEmit<(e: 'page-change', index: number) => void>()
|
||||
|
||||
const { count, page } = toRefs(props)
|
||||
const pagesCount = computed(() => Math.ceil(count.value / limit))
|
||||
const isActive = (index: number) => page.value === index
|
||||
const onPageChange = (index: number) => emit('page-change', index)
|
||||
|
||||
return {
|
||||
pagesCount,
|
||||
isActive,
|
||||
onPageChange,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,20 +3,14 @@
|
|||
<div class="container">
|
||||
<h1>{{ article.title }}</h1>
|
||||
|
||||
<ArticleDetailMeta
|
||||
:article="article"
|
||||
@update="updateArticle"
|
||||
/>
|
||||
<ArticleDetailMeta :article="article" @update="updateArticle" />
|
||||
</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"
|
||||
/>
|
||||
<div class="col-md-12" v-html="articleHandledBody" />
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<ul class="tag-list">
|
||||
<li
|
||||
|
|
@ -29,33 +23,22 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<hr />
|
||||
|
||||
<div class="article-actions">
|
||||
<ArticleDetailMeta
|
||||
:article="article"
|
||||
@update="updateArticle"
|
||||
/>
|
||||
<ArticleDetailMeta :article="article" @update="updateArticle" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import DOMPurify from 'dompurify'
|
||||
import md2html from 'marked'
|
||||
import { computed, defineComponent, reactive } from 'vue'
|
||||
import { computed, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import ArticleDetailMeta from './ArticleDetailMeta.vue'
|
||||
|
||||
import { getArticle } from '../services/article/getArticle'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticleDetail',
|
||||
components: {
|
||||
ArticleDetailMeta,
|
||||
},
|
||||
async setup () {
|
||||
const route = useRoute()
|
||||
const slug = route.params.slug as string
|
||||
const article = reactive<Article>(await getArticle(slug))
|
||||
|
|
@ -65,13 +48,4 @@ export default defineComponent({
|
|||
const updateArticle = (newArticle: Article) => {
|
||||
Object.assign(article, newArticle)
|
||||
}
|
||||
|
||||
return {
|
||||
article,
|
||||
articleHandledBody,
|
||||
slug,
|
||||
updateArticle,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -41,24 +41,17 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineEmit, defineProps } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticleDetailComment',
|
||||
props: {
|
||||
comment: { type: Object as PropType<ArticleComment>, required: true },
|
||||
username: { type: String as PropType<string | undefined>, default: undefined },
|
||||
},
|
||||
emits: {
|
||||
'remove-comment': () => true,
|
||||
},
|
||||
setup (props) {
|
||||
return {
|
||||
showRemove: computed(() => (
|
||||
const props = defineProps<{
|
||||
comment: ArticleComment
|
||||
username?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmit<(e: 'remove-comment') => boolean>()
|
||||
|
||||
const showRemove = computed(() => (
|
||||
props.username !== undefined && props.username === props.comment.author.username
|
||||
)),
|
||||
}
|
||||
},
|
||||
})
|
||||
))
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import ArticleDetailCommentsForm from './ArticleDetailCommentsForm.vue'
|
||||
|
|
@ -25,13 +25,6 @@ import { deleteComment } from '../services/comment/postComment'
|
|||
|
||||
import { user } from '../store/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticleDetailComments',
|
||||
components: {
|
||||
ArticleDetailCommentsForm,
|
||||
ArticleDetailComment,
|
||||
},
|
||||
async setup () {
|
||||
const route = useRoute()
|
||||
const slug = route.params.slug as string
|
||||
|
||||
|
|
@ -50,13 +43,4 @@ export default defineComponent({
|
|||
|
||||
comments.value = await getCommentsByArticle(slug)
|
||||
|
||||
return {
|
||||
comments,
|
||||
slug,
|
||||
username,
|
||||
addComment,
|
||||
removeComment,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@
|
|||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, defineProps, defineEmit } from 'vue'
|
||||
|
||||
import { useProfile } from '../composable/useProfile'
|
||||
|
||||
|
|
@ -44,15 +44,12 @@ import { postComment } from '../services/comment/postComment'
|
|||
|
||||
import { user, checkAuthorization } from '../store/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticleDetailCommentsForm',
|
||||
props: {
|
||||
articleSlug: { type: String, required: true },
|
||||
},
|
||||
emits: {
|
||||
'add-comment': (comment: ArticleComment) => !!comment.id,
|
||||
},
|
||||
setup (props, { emit }) {
|
||||
const props = defineProps<{
|
||||
articleSlug: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmit<(e: 'add-comment', comment: ArticleComment) => void>()
|
||||
|
||||
const username = computed(() => checkAuthorization(user) ? user.value.username : '')
|
||||
const { profile } = useProfile({ username })
|
||||
|
||||
|
|
@ -64,11 +61,4 @@ export default defineComponent({
|
|||
comment.value = ''
|
||||
}
|
||||
|
||||
return {
|
||||
profile,
|
||||
comment,
|
||||
submitComment,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<template>
|
||||
<div class="article-meta">
|
||||
<AppLink
|
||||
name="profile"
|
||||
:params="{username: article.author.username}"
|
||||
>
|
||||
<img :src="article.author.image">
|
||||
<AppLink name="profile" :params="{ username: article.author.username }">
|
||||
<img :src="article.author.image" />
|
||||
</AppLink>
|
||||
|
||||
<div class="info">
|
||||
|
|
@ -16,7 +13,9 @@
|
|||
{{ article.author.username }}
|
||||
</AppLink>
|
||||
|
||||
<span class="date">{{ (new Date(article.createdAt)).toLocaleDateString() }}</span>
|
||||
<span class="date">{{
|
||||
new Date(article.createdAt).toLocaleDateString()
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
|
@ -26,7 +25,8 @@
|
|||
@click="toggleFollow"
|
||||
>
|
||||
<i class="ion-plus-round space" />
|
||||
{{ article.author.following ? "Unfollow" : "Follow" }} {{ article.author.username }}
|
||||
{{ article.author.following ? "Unfollow" : "Follow" }}
|
||||
{{ article.author.username }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
@click="favoriteArticle"
|
||||
>
|
||||
<i class="ion-heart space" />
|
||||
{{ article.favorited ? 'Unfavorite' : 'Favorite' }} Article
|
||||
{{ article.favorited ? "Unfavorite" : "Favorite" }} Article
|
||||
<span class="counter">({{ article.favoritesCount }})</span>
|
||||
</button>
|
||||
|
||||
|
|
@ -59,8 +59,8 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, toRefs } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineEmit, defineProps, toRefs } from 'vue'
|
||||
|
||||
import { deleteArticle } from '../services/article/deleteArticle'
|
||||
|
||||
|
|
@ -70,15 +70,12 @@ import { useFollow } from '../composable/useFollowProfile'
|
|||
import { user, checkAuthorization } from '../store/user'
|
||||
import { routerPush } from '../router'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticleDetailMeta',
|
||||
props: {
|
||||
article: { type: Object as PropType<Article>, required: true },
|
||||
},
|
||||
emits: {
|
||||
update: (article: Article) => !!article.slug,
|
||||
},
|
||||
setup (props, { emit }) {
|
||||
const props = defineProps<{
|
||||
article: Article
|
||||
}>()
|
||||
|
||||
const emit = defineEmit<(e: 'update', article: Article) => void>()
|
||||
|
||||
const { article } = toRefs(props)
|
||||
const displayEditButton = computed(() => checkAuthorization(user) && user.value.username === article.value.author.username)
|
||||
const displayFollowButton = computed(() => user.value?.username !== article.value.author.username)
|
||||
|
|
@ -102,18 +99,6 @@ export default defineComponent({
|
|||
emit('update', newArticle)
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
displayEditButton,
|
||||
displayFollowButton,
|
||||
onDelete,
|
||||
favoriteProcessGoing,
|
||||
favoriteArticle,
|
||||
followProcessGoing,
|
||||
toggleFollow,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,14 @@
|
|||
<template>
|
||||
<ArticlesListNavigation
|
||||
v-bind="$attrs"
|
||||
:tag="tag"
|
||||
:username="username"
|
||||
/>
|
||||
<ArticlesListNavigation v-bind="$attrs" :tag="tag" :username="username" />
|
||||
|
||||
<div
|
||||
v-if="articlesDownloading"
|
||||
class="article-preview"
|
||||
>
|
||||
<div v-if="articlesDownloading" class="article-preview">
|
||||
Articles are downloading...
|
||||
</div>
|
||||
<div
|
||||
v-else-if="articles.length === 0"
|
||||
class="article-preview"
|
||||
>
|
||||
<div v-else-if="articles.length === 0" class="article-preview">
|
||||
No articles are here... yet.
|
||||
</div>
|
||||
<template v-else>
|
||||
<ArticlePreview
|
||||
<ArticlesListArticlePreview
|
||||
v-for="(article, index) in articles"
|
||||
:key="article.slug"
|
||||
:article="article"
|
||||
|
|
@ -33,43 +23,23 @@
|
|||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArticlesListNavigation from './ArticlesListNavigation.vue'
|
||||
import ArticlesListArticlePreview from './ArticlesListArticlePreview.vue'
|
||||
import AppPagination from './AppPagination.vue'
|
||||
|
||||
import { useArticles } from '../composable/useArticles'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticlesList',
|
||||
components: {
|
||||
ArticlePreview: ArticlesListArticlePreview,
|
||||
AppPagination,
|
||||
ArticlesListNavigation,
|
||||
},
|
||||
|
||||
async setup () {
|
||||
const {
|
||||
fetchArticles, articlesDownloading,
|
||||
articlesCount, articles, updateArticle,
|
||||
page, changePage,
|
||||
tag, username,
|
||||
fetchArticles,
|
||||
articlesDownloading,
|
||||
articlesCount,
|
||||
articles,
|
||||
updateArticle,
|
||||
page,
|
||||
changePage,
|
||||
tag,
|
||||
username,
|
||||
} = useArticles()
|
||||
|
||||
await fetchArticles()
|
||||
|
||||
return {
|
||||
articlesDownloading,
|
||||
articles,
|
||||
articlesCount,
|
||||
page,
|
||||
changePage,
|
||||
updateArticle,
|
||||
tag,
|
||||
username,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
>
|
||||
{{ article.author.username }}
|
||||
</AppLink>
|
||||
<span class="date">{{ new Date(article.createdAt).toDateString() }}</span>
|
||||
<span class="date">{{
|
||||
new Date(article.createdAt).toDateString()
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
|
@ -49,29 +51,19 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineEmit, defineProps } from 'vue'
|
||||
import { useFavoriteArticle } from '../composable/useFavoriteArticle'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticlesListArticlePreview',
|
||||
props: {
|
||||
article: { type: Object as PropType<Article>, required: true },
|
||||
},
|
||||
emits: {
|
||||
update: (article: Article) => !!article.slug,
|
||||
},
|
||||
setup (props, { emit }) {
|
||||
const props = defineProps<{
|
||||
article: Article;
|
||||
}>()
|
||||
|
||||
const emit = defineEmit<(e: 'update', article: Article) => void>()
|
||||
|
||||
const { favoriteProcessGoing, favoriteArticle } = useFavoriteArticle({
|
||||
isFavorited: computed(() => props.article.favorited),
|
||||
articleSlug: computed(() => props.article.slug),
|
||||
onUpdate: (newArticle: Article): void => emit('update', newArticle),
|
||||
})
|
||||
|
||||
return {
|
||||
favoriteProcessGoing,
|
||||
favoriteArticle,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -22,35 +22,32 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import type { RouteParams } from 'vue-router'
|
||||
import type { AppRouteNames } from '../router'
|
||||
|
||||
import type { ArticlesType } from '../composable/useArticles'
|
||||
|
||||
import { computed, defineProps } from 'vue'
|
||||
import { isAuthorized } from '../store/user'
|
||||
|
||||
interface ArticlesListNavLink {
|
||||
name: ArticlesType
|
||||
routeName: AppRouteNames
|
||||
routeParams?: Partial<RouteParams>
|
||||
title: string
|
||||
icon?: string
|
||||
name: ArticlesType;
|
||||
routeName: AppRouteNames;
|
||||
routeParams?: Partial<RouteParams>;
|
||||
title: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticlesListNavigation',
|
||||
props: {
|
||||
useGlobalFeed: { type: Boolean, default: false },
|
||||
useMyFeed: { type: Boolean, default: false },
|
||||
useTagFeed: { type: Boolean, default: false },
|
||||
useUserFeed: { type: Boolean, default: false },
|
||||
useUserFavorited: { type: Boolean, default: false },
|
||||
tag: { type: String, required: true },
|
||||
username: { type: String, required: true },
|
||||
},
|
||||
setup (props) {
|
||||
const props = defineProps<{
|
||||
tag: string;
|
||||
username: string;
|
||||
useGlobalFeed?: boolean;
|
||||
useMyFeed?: boolean;
|
||||
useTagFeed?: boolean;
|
||||
useUserFeed?: boolean;
|
||||
useUserFavorited?: boolean;
|
||||
}>()
|
||||
|
||||
const allLinks = computed<ArticlesListNavLink[]>(() => [
|
||||
{
|
||||
name: 'global-feed',
|
||||
|
|
@ -85,18 +82,14 @@ export default defineComponent({
|
|||
])
|
||||
|
||||
const show = computed<Record<ArticlesType, boolean>>(() => ({
|
||||
'global-feed': props.useGlobalFeed,
|
||||
'my-feed': props.useMyFeed && isAuthorized.value,
|
||||
'tag-feed': props.useTagFeed && props.tag !== '',
|
||||
'user-feed': props.useUserFeed && props.username !== '',
|
||||
'user-favorites-feed': props.useUserFavorited && props.username !== '',
|
||||
'global-feed': props.useGlobalFeed ?? false,
|
||||
'my-feed': (props.useMyFeed && isAuthorized.value) ?? false,
|
||||
'tag-feed': (props.useTagFeed && props.tag !== '') ?? false,
|
||||
'user-feed': (props.useUserFeed && props.username !== '') ?? false,
|
||||
'user-favorites-feed': (props.useUserFavorited && props.username !== '') ?? false,
|
||||
}))
|
||||
|
||||
const links = computed<ArticlesListNavLink[]>(() => allLinks.value.filter(link => show.value[link.name]))
|
||||
|
||||
return {
|
||||
links,
|
||||
}
|
||||
},
|
||||
})
|
||||
const links = computed<ArticlesListNavLink[]>(() =>
|
||||
allLinks.value.filter((link) => show.value[link.name]),
|
||||
)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -14,20 +14,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { useTags } from '../composable/useTags'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PopularTags',
|
||||
async setup () {
|
||||
const { tags, fetchTags } = useTags()
|
||||
|
||||
await fetchTags()
|
||||
|
||||
return {
|
||||
tags,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ export function useArticles () {
|
|||
}
|
||||
|
||||
export type ArticlesType = 'global-feed' | 'my-feed' | 'tag-feed' | 'user-feed' | 'user-favorites-feed'
|
||||
|
||||
export const articlesTypes: ArticlesType[] = ['global-feed', 'my-feed', 'tag-feed', 'user-feed', 'user-favorites-feed']
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
export const isArticlesType = (type: any): type is ArticlesType => articlesTypes.includes(type)
|
||||
|
|
|
|||
|
|
@ -28,17 +28,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArticleDetail from '../components/ArticleDetail.vue'
|
||||
import ArticleDetailComments from '../components/ArticleDetailComments.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ArticlePage',
|
||||
components: {
|
||||
ArticleDetail,
|
||||
ArticleDetailComments,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, reactive, ref } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { getArticle } from '../services/article/getArticle'
|
||||
import { postArticle, putArticle } from '../services/article/postArticle'
|
||||
|
|
@ -78,9 +78,6 @@ interface FormState {
|
|||
tagList: string[];
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditArticlePage',
|
||||
setup () {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const slug = computed<string>(() => route.params.slug as string)
|
||||
|
|
@ -124,14 +121,4 @@ export default defineComponent({
|
|||
}
|
||||
return router.push({ name: 'article', params: { slug: article.slug } })
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
onSubmit,
|
||||
newTag,
|
||||
addTag,
|
||||
removeTag,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -43,17 +43,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArticlesList from '../components/ArticlesList.vue'
|
||||
import PopularTags from '../components/PopularTags.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HomePage',
|
||||
components: {
|
||||
ArticlesList,
|
||||
PopularTags,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,39 +3,26 @@
|
|||
<div class="container page">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3 col-xs-12">
|
||||
<h1 class="text-xs-center">
|
||||
Sign in
|
||||
</h1>
|
||||
<h1 class="text-xs-center">Sign in</h1>
|
||||
<p class="text-xs-center">
|
||||
<AppLink name="register">
|
||||
Need an account?
|
||||
</AppLink>
|
||||
<AppLink name="register"> Need an account? </AppLink>
|
||||
</p>
|
||||
|
||||
<ul class="error-messages">
|
||||
<li
|
||||
v-for="(error, field) in errors"
|
||||
:key="field"
|
||||
>
|
||||
{{ field }} {{ error ? error[0] : '' }}
|
||||
<li v-for="(error, field) in errors" :key="field">
|
||||
{{ field }} {{ error ? error[0] : "" }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form
|
||||
ref="formRef"
|
||||
@submit.prevent="login"
|
||||
>
|
||||
<fieldset
|
||||
class="form-group"
|
||||
aria-required="true"
|
||||
>
|
||||
<form ref="formRef" @submit.prevent="login">
|
||||
<fieldset class="form-group" aria-required="true">
|
||||
<input
|
||||
v-model="form.email"
|
||||
class="form-control form-control-lg"
|
||||
type="email"
|
||||
required
|
||||
placeholder="Email"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
|
|
@ -44,7 +31,7 @@
|
|||
type="password"
|
||||
required
|
||||
placeholder="Password"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<button
|
||||
class="btn btn-lg btn-primary pull-xs-right"
|
||||
|
|
@ -60,17 +47,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import type { PostLoginForm, PostLoginErrors } from '../services/auth/postLogin'
|
||||
|
||||
import { reactive, ref } from 'vue'
|
||||
import { routerPush } from '../router'
|
||||
|
||||
import { postLogin, PostLoginForm, PostLoginErrors } from '../services/auth/postLogin'
|
||||
|
||||
import { postLogin } from '../services/auth/postLogin'
|
||||
import { updateUser } from '../store/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LoginPage',
|
||||
setup () {
|
||||
const formRef = ref<HTMLFormElement | null>(null)
|
||||
const form = reactive<PostLoginForm>({
|
||||
email: '',
|
||||
|
|
@ -90,13 +74,4 @@ export default defineComponent({
|
|||
errors.value = await result.value.getErrors()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
formRef,
|
||||
form,
|
||||
login,
|
||||
errors,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@
|
|||
@click="toggleFollow"
|
||||
>
|
||||
<i class="ion-plus-round space" />
|
||||
{{ profile.following ? "Unfollow" : "Follow" }} {{ profile.username }}
|
||||
{{ profile.following ? "Unfollow" : "Follow" }}
|
||||
{{ profile.username }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
|
@ -66,23 +67,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import ArticlesList from '../components/ArticlesList.vue'
|
||||
|
||||
import { useProfile } from '../composable/useProfile'
|
||||
import { useFollow } from '../composable/useFollowProfile'
|
||||
|
||||
import { user, checkAuthorization } from '../store/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfilePage',
|
||||
components: {
|
||||
ArticlesList,
|
||||
},
|
||||
setup () {
|
||||
const route = useRoute()
|
||||
const username = computed<string>(() => route.params.username as string)
|
||||
|
||||
|
|
@ -96,17 +88,6 @@ export default defineComponent({
|
|||
|
||||
const showEdit = computed<boolean>(() => checkAuthorization(user) && user.value.username === username.value)
|
||||
const showFollow = computed<boolean>(() => user.value?.username !== username.value)
|
||||
|
||||
return {
|
||||
profile,
|
||||
showEdit,
|
||||
showFollow,
|
||||
followProcessGoing,
|
||||
toggleFollow,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -114,6 +95,6 @@ export default defineComponent({
|
|||
margin-right: 4px;
|
||||
}
|
||||
.align-left {
|
||||
text-align: left
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,28 +3,18 @@
|
|||
<div class="container page">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3 col-xs-12">
|
||||
<h1 class="text-xs-center">
|
||||
Sign up
|
||||
</h1>
|
||||
<h1 class="text-xs-center">Sign up</h1>
|
||||
<p class="text-xs-center">
|
||||
<AppLink name="login">
|
||||
Have an account?
|
||||
</AppLink>
|
||||
<AppLink name="login"> Have an account? </AppLink>
|
||||
</p>
|
||||
|
||||
<ul class="error-messages">
|
||||
<li
|
||||
v-for="(error, field) in errors"
|
||||
:key="field"
|
||||
>
|
||||
{{ field }} {{ error ? error[0] : '' }}
|
||||
<li v-for="(error, field) in errors" :key="field">
|
||||
{{ field }} {{ error ? error[0] : "" }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form
|
||||
ref="formRef"
|
||||
@submit.prevent="register"
|
||||
>
|
||||
<form ref="formRef" @submit.prevent="register">
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
v-model="form.username"
|
||||
|
|
@ -32,7 +22,7 @@
|
|||
type="text"
|
||||
required
|
||||
placeholder="Your Name"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
|
|
@ -41,7 +31,7 @@
|
|||
type="email"
|
||||
required
|
||||
placeholder="Email"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
|
|
@ -51,7 +41,7 @@
|
|||
:minLength="8"
|
||||
required
|
||||
placeholder="Password"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<button
|
||||
type="submit"
|
||||
|
|
@ -67,17 +57,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import type { PostRegisterForm, PostRegisterErrors } from '../services/auth/postRegister'
|
||||
|
||||
import { reactive, ref } from 'vue'
|
||||
import { routerPush } from '../router'
|
||||
|
||||
import { postRegister, PostRegisterForm, PostRegisterErrors } from '../services/auth/postRegister'
|
||||
|
||||
import { postRegister } from '../services/auth/postRegister'
|
||||
import { updateUser } from '../store/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RegisterPage',
|
||||
setup () {
|
||||
const formRef = ref<HTMLFormElement | null>(null)
|
||||
const form = reactive<PostRegisterForm>({
|
||||
username: '',
|
||||
|
|
@ -98,13 +85,4 @@ export default defineComponent({
|
|||
errors.value = await result.value.getErrors()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
formRef,
|
||||
form,
|
||||
register,
|
||||
errors,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
<div class="container page">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3 col-xs-12">
|
||||
<h1 class="text-xs-center">
|
||||
Your Settings
|
||||
</h1>
|
||||
<h1 class="text-xs-center">Your Settings</h1>
|
||||
|
||||
<form @submit.prevent="onSubmit">
|
||||
<fieldset>
|
||||
|
|
@ -15,7 +13,7 @@
|
|||
type="text"
|
||||
class="form-control"
|
||||
placeholder="URL of profile picture"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
|
|
@ -23,7 +21,7 @@
|
|||
type="text"
|
||||
class="form-control form-control-lg"
|
||||
placeholder="Your name"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<textarea
|
||||
|
|
@ -39,7 +37,7 @@
|
|||
type="email"
|
||||
class="form-control form-control-lg"
|
||||
placeholder="Email"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
|
|
@ -47,7 +45,7 @@
|
|||
type="password"
|
||||
class="form-control form-control-lg"
|
||||
placeholder="New Password"
|
||||
>
|
||||
/>
|
||||
</fieldset>
|
||||
<button
|
||||
class="btn btn-lg btn-primary pull-xs-right"
|
||||
|
|
@ -59,12 +57,9 @@
|
|||
</fieldset>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
<hr />
|
||||
|
||||
<button
|
||||
class="btn btn-outline-danger"
|
||||
@click="onLogout"
|
||||
>
|
||||
<button class="btn btn-outline-danger" @click="onLogout">
|
||||
Or click here to logout.
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -73,17 +68,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, reactive } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import type { PutProfileForm } from '../services/profile/putProfile'
|
||||
|
||||
import { computed, onMounted, reactive } from 'vue'
|
||||
import { routerPush } from '../router'
|
||||
|
||||
import { putProfile, PutProfileForm } from '../services/profile/putProfile'
|
||||
|
||||
import { putProfile } from '../services/profile/putProfile'
|
||||
import { user, checkAuthorization, updateUser } from '../store/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SettingsPage',
|
||||
setup () {
|
||||
const form = reactive<PutProfileForm>({})
|
||||
|
||||
const onSubmit = async () => {
|
||||
|
|
@ -114,13 +106,4 @@ export default defineComponent({
|
|||
form.email === user.value?.email &&
|
||||
!form.password
|
||||
))
|
||||
|
||||
return {
|
||||
form,
|
||||
onSubmit,
|
||||
isButtonDisabled,
|
||||
onLogout,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@ export const router = createRouter({
|
|||
})
|
||||
|
||||
export function routerPush (name: AppRouteNames, params?: RouteParams): ReturnType<typeof router.push> {
|
||||
if (params !== undefined) return router.push({ name, params })
|
||||
else return router.push({ name })
|
||||
if (params !== undefined) {
|
||||
return router.push({
|
||||
name,
|
||||
params,
|
||||
})
|
||||
} else {
|
||||
return router.push({ name })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue