(favorite) save favorite repos

This commit is contained in:
2021-03-19 23:01:31 +01:00
parent 2faabb6c0e
commit 5fcf3c9df5
11 changed files with 308 additions and 44 deletions

26
src/components/GoBack.vue Normal file
View File

@@ -0,0 +1,26 @@
<template>
<button class="button is-primary go-back" @click="back">
<img src="@/assets/icons/left-arrow.svg" alt="back" />
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { useRouter } from 'vue-router'
export default defineComponent({
name: 'GoBack',
setup() {
const { go } = useRouter()
return {
back: () => go(-1)
}
}
})
</script>
<style lang="scss" scoped>
.go-back {
}
</style>

View File

@@ -1,26 +1,56 @@
<template> <template>
<div class="welcome-world"> <div class="welcome-world">
<h1 class="title is-1">About "Lite notes"</h1> <div class="columns">
<div class="column">
<ol> <h3 class="title is-3">Lite Note</h3>
<li> <h4 class="subtitle is-4">Get started</h4>
Take notes your favorite <ol>
<router-link :to="{ name: 'TextEditor' }">text editor</router-link> <li>
</li> Take notes your favorite
<li> <router-link :to="{ name: 'TextEditor' }">text editor</router-link>
Push to GitHub </li>
</li> <li>
<li> Push to GitHub
Read it here </li>
</li> <li>
<li> Read it here
Share it with an URL </li>
</li> <li>
</ol> Share it with an URL
</li>
<router-link :to="{ name: 'RepoList' }" v-if="isLogged" </ol>
>go to repos</router-link </div>
> <div class="column">
<p>
<router-link :to="{ name: 'RepoList' }" v-if="isLogged"
>Manage your repos</router-link
>
</p>
<section v-if="savedFavoriteRepos.length">
<h4 class="subtitle is-4">
</h4>
<ul>
<li
v-for="favoriteRepo in savedFavoriteRepos"
:key="favoriteRepo.id"
>
<router-link
:to="{
name: 'Home',
params: {
user: username,
repo: favoriteRepo.name
}
}"
>
{{ favoriteRepo.name }}
</router-link>
</li>
</ul>
</section>
</div>
</div>
<form @submit.prevent> <form @submit.prevent>
<div class="columns is-centered is-vcentered to-user-repo"> <div class="columns is-centered is-vcentered to-user-repo">
@@ -90,13 +120,15 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { useForm } from '@/hooks/useForm.hook' import { useForm } from '@/hooks/useForm.hook'
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook' import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { useFavoriteRepos } from '@/modules/repo/hooks/useFavoriteRepos.hook'
export default defineComponent({ export default defineComponent({
name: 'WelcomeWord', name: 'WelcomeWord',
setup() { setup() {
const { isLogged } = useGitHubLogin() const { isLogged, username } = useGitHubLogin()
const { savedFavoriteRepos } = useFavoriteRepos()
return { ...useForm(), isLogged } return { ...useForm(), isLogged, username, savedFavoriteRepos }
} }
}) })
</script> </script>
@@ -107,6 +139,11 @@ export default defineComponent({
margin: auto; margin: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
h3,
h4 {
text-align: center;
}
} }
footer { footer {

View File

@@ -1,3 +1,4 @@
export enum DataType { export enum DataType {
GithubAccessToken = 'GithubAccessToken' GithubAccessToken = 'GithubAccessToken',
FavoriteRepo = 'FavoriteRepo'
} }

View File

@@ -10,10 +10,11 @@ interface GetAllParams {
prefix?: string prefix?: string
includeDocs?: boolean includeDocs?: boolean
includeAttachments?: boolean includeAttachments?: boolean
keys?: string[]
} }
class Data { class Data {
private locale = new PouchDb('local-db', { private locale = new PouchDb('lite-note', {
adapter: 'indexeddb' adapter: 'indexeddb'
}) })
@@ -28,6 +29,24 @@ class Data {
} }
} }
public async update<DT extends DataType>(model: Model<DT>): Promise<boolean> {
try {
if (model._id) {
const oldModel = await this.get(model._id)
if (oldModel) {
const result = await this.locale.put({ ...oldModel, ...model })
return result.ok
}
}
const result = await this.locale.put(model)
return result.ok
} catch (error) {
console.warn(error)
return false
}
}
public async remove(id: string): Promise<boolean> { public async remove(id: string): Promise<boolean> {
try { try {
const doc = await this.get(id) const doc = await this.get(id)
@@ -57,8 +76,19 @@ class Data {
public async getAll<DT extends DataType, T extends Model<DT>>({ public async getAll<DT extends DataType, T extends Model<DT>>({
prefix, prefix,
includeDocs = true, includeDocs = true,
includeAttachments = false includeAttachments = false,
keys = []
}: GetAllParams): Promise<T[]> { }: GetAllParams): Promise<T[]> {
if (keys.length) {
const response = await this.locale.allDocs({
include_docs: includeDocs,
attachments: includeAttachments,
keys: keys.map((key) => this.generateId(prefix, key))
})
return response.rows.map((row) => row.doc).filter((doc) => !!doc) as T[]
}
const response = await this.locale.allDocs({ const response = await this.locale.allDocs({
include_docs: includeDocs, include_docs: includeDocs,
attachments: includeAttachments, attachments: includeAttachments,
@@ -69,7 +99,11 @@ class Data {
return response.rows.map((row) => row.doc) as T[] return response.rows.map((row) => row.doc) as T[]
} }
public generateId(type: DataType, id?: string) { public generateId(type?: DataType | string, id?: string) {
if (!type) {
return id || nanoid()
}
return `${type}-${id || nanoid()}` return `${type}-${id || nanoid()}`
} }
} }

View File

@@ -1,10 +1,11 @@
import { Octokit } from '@octokit/rest' import { Octokit } from '@octokit/rest'
import { RepoBase } from '@/modules/interfaces/RepoBase'
import { useAsyncState } from '@vueuse/core' import { useAsyncState } from '@vueuse/core'
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook' import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
export const useRepos = () => { export const useRepos = () => {
const { username, accessToken } = useGitHubLogin() const { username, accessToken } = useGitHubLogin()
const repos = useAsyncState(async () => { const repos = useAsyncState<RepoBase[]>(async () => {
if (!accessToken.value || !username.value) { if (!accessToken.value || !username.value) {
return [] return []
} }
@@ -18,7 +19,11 @@ export const useRepos = () => {
per_page: 100 per_page: 100
}) })
return repoList.data.items.map((item) => item.name) return repoList.data.items.map((item) => ({
id: `${item.id}`,
name: item.name,
isPrivate: item.private
}))
}, []) }, [])
return { return {

View File

@@ -0,0 +1,5 @@
export interface RepoBase {
id: string
name: string
isPrivate: boolean
}

View File

@@ -0,0 +1,8 @@
import { DataType } from '@/data/DataType.enum'
import { Model } from '@/data/models/Model'
export interface FavoriteRepo extends Model<DataType.FavoriteRepo> {
isFavorite: boolean
isPrivate: boolean
name: string
}

View File

@@ -0,0 +1,47 @@
import { computed, onMounted, ref } from 'vue'
import { DataType } from '@/data/DataType.enum'
import { FavoriteRepo } from '@/modules/models/FavoriteRepo'
import { RepoBase } from '@/modules/interfaces/RepoBase'
import { data } from '@/data/data'
import { useRepos } from '@/hooks/useRepos.hook'
export const useFavoriteRepos = () => {
const { repos } = useRepos()
const savedRepos = ref<FavoriteRepo[]>([])
const getFavorites = async () => {
savedRepos.value = await data.getAll<DataType.FavoriteRepo, FavoriteRepo>({
prefix: DataType.FavoriteRepo,
keys: repos.value.map((repo) => repo.id)
})
}
const savedFavoriteRepos = computed(() =>
savedRepos.value.filter((repo) => repo.isFavorite)
)
onMounted(() => {
getFavorites()
})
const toggleFavorite = async (repo: RepoBase, isFavorite: boolean) => {
const favorite: FavoriteRepo = {
_id: data.generateId(DataType.FavoriteRepo, repo.id),
$type: DataType.FavoriteRepo,
isFavorite,
name: repo.name,
isPrivate: repo.isPrivate
}
await data.update(favorite)
await getFavorites()
}
return {
savedRepos,
savedFavoriteRepos,
addFavorite: (repo: RepoBase) => toggleFavorite(repo, true),
removeFavorite: (repo: RepoBase) => toggleFavorite(repo, false)
}
}

View File

@@ -0,0 +1,42 @@
import { RepoBase } from '@/modules/interfaces/RepoBase'
import { computed } from 'vue'
import { useFavoriteRepos } from '@/modules/repo/hooks/useFavoriteRepos.hook'
import { useRepos } from '@/hooks/useRepos.hook'
export const useRepoList = () => {
const { savedFavoriteRepos, addFavorite, removeFavorite } = useFavoriteRepos()
const { repos } = useRepos()
const favoriteRepos = computed(() => {
return repos.value.filter((repo) =>
savedFavoriteRepos.value.find(
(fav) => fav._id?.includes(repo.id) ?? false
)
)
})
const otherRepos = computed(() => {
return repos.value.filter(
(repo) => !favoriteRepos.value.find((favorite) => favorite.id === repo.id)
)
})
const favoriteCheckboxes = computed(() =>
favoriteRepos.value.map((favorite) => favorite.id)
)
const toggleCheckbox = async (repo: RepoBase) => {
if (favoriteCheckboxes.value.includes(repo.id)) {
await removeFavorite(repo)
} else {
await addFavorite(repo)
}
}
return {
favoriteRepos,
otherRepos,
favoriteCheckboxes,
toggleCheckbox
}
}

View File

@@ -1,8 +1,8 @@
@charset "utf-8"; @charset "utf-8";
@import url('https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap');
$primary: #2C3A47; $primary: #2c3a47;
$link: #58B19F; $link: #58b19f;
@import '~bulma/bulma.sass'; @import '~bulma/bulma.sass';

View File

@@ -1,32 +1,88 @@
<template> <template>
<div class="repo-list"> <div class="repo-list">
<h1>Repositories</h1> <go-back />
<h1 class="title is-1">Repositories</h1>
<span v-if="!isReady">loading...</span> <span v-if="!isReady">loading...</span>
<ul> <div v-else class="columns is-centered">
<li v-for="repo in repos" :key="repo"> <div class="column is-one-third">
<router-link <table class="table is-striped is-hoverable is-fullwidth">
:to="{ name: 'Home', params: { user: username, repo: repo } }" <tr v-for="repo in favoriteRepos" :key="repo.id">
> <td>
{{ repo }} <input
</router-link> type="checkbox"
</li> name="favorites"
</ul> :value="repo.id"
:checked="favoriteCheckboxes.includes(repo.id)"
@click="toggleCheckbox(repo)"
/>
</td>
<td>
<span v-if="repo.isPrivate">🔏</span>
<router-link
:to="{
name: 'Home',
params: { user: username, repo: repo.name }
}"
>
{{ repo.name }}
</router-link>
</td>
</tr>
<tr v-for="repo in otherRepos" :key="repo.id">
<td>
<input
type="checkbox"
name="favorites"
:value="repo.id"
:checked="favoriteCheckboxes.includes(repo.id)"
@click="toggleCheckbox(repo)"
/>
</td>
<td>
<router-link
:to="{
name: 'Home',
params: { user: username, repo: repo.name }
}"
>
{{ repo.name }}
</router-link>
</td>
</tr>
</table>
</div>
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { useRepos } from '@/hooks/useRepos.hook'
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook' import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { useRepoList } from '@/modules/repo/hooks/userRepoList.hook'
import { useRepos } from '@/hooks/useRepos.hook'
import GoBack from '@/components/GoBack.vue'
export default defineComponent({ export default defineComponent({
name: 'RepoList', name: 'RepoList',
components: { GoBack },
setup() { setup() {
const { username } = useGitHubLogin() const { username } = useGitHubLogin()
const { isReady } = useRepos()
const {
favoriteRepos,
otherRepos,
favoriteCheckboxes,
toggleCheckbox
} = useRepoList()
return { return {
...useRepos(), isReady,
username username,
favoriteRepos,
otherRepos,
favoriteCheckboxes,
toggleCheckbox
} }
} }
}) })
@@ -35,6 +91,9 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.repo-list { .repo-list {
flex: 1; flex: 1;
padding: 1rem;
text-align: center; text-align: center;
overflow-y: auto;
} }
</style> </style>