✨ (favorite) save favorite repos
This commit is contained in:
26
src/components/GoBack.vue
Normal file
26
src/components/GoBack.vue
Normal 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>
|
||||
@@ -1,26 +1,56 @@
|
||||
<template>
|
||||
<div class="welcome-world">
|
||||
<h1 class="title is-1">About "Lite notes"</h1>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
Take notes your favorite
|
||||
<router-link :to="{ name: 'TextEditor' }">text editor</router-link>
|
||||
</li>
|
||||
<li>
|
||||
Push to GitHub
|
||||
</li>
|
||||
<li>
|
||||
Read it here
|
||||
</li>
|
||||
<li>
|
||||
Share it with an URL
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<router-link :to="{ name: 'RepoList' }" v-if="isLogged"
|
||||
>go to repos</router-link
|
||||
>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h3 class="title is-3">Lite Note</h3>
|
||||
<h4 class="subtitle is-4">Get started</h4>
|
||||
<ol>
|
||||
<li>
|
||||
Take notes your favorite
|
||||
<router-link :to="{ name: 'TextEditor' }">text editor</router-link>
|
||||
</li>
|
||||
<li>
|
||||
Push to GitHub
|
||||
</li>
|
||||
<li>
|
||||
Read it here
|
||||
</li>
|
||||
<li>
|
||||
Share it with an URL
|
||||
</li>
|
||||
</ol>
|
||||
</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>
|
||||
<div class="columns is-centered is-vcentered to-user-repo">
|
||||
@@ -90,13 +120,15 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { useForm } from '@/hooks/useForm.hook'
|
||||
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
|
||||
import { useFavoriteRepos } from '@/modules/repo/hooks/useFavoriteRepos.hook'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WelcomeWord',
|
||||
setup() {
|
||||
const { isLogged } = useGitHubLogin()
|
||||
const { isLogged, username } = useGitHubLogin()
|
||||
const { savedFavoriteRepos } = useFavoriteRepos()
|
||||
|
||||
return { ...useForm(), isLogged }
|
||||
return { ...useForm(), isLogged, username, savedFavoriteRepos }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -107,6 +139,11 @@ export default defineComponent({
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export enum DataType {
|
||||
GithubAccessToken = 'GithubAccessToken'
|
||||
GithubAccessToken = 'GithubAccessToken',
|
||||
FavoriteRepo = 'FavoriteRepo'
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ interface GetAllParams {
|
||||
prefix?: string
|
||||
includeDocs?: boolean
|
||||
includeAttachments?: boolean
|
||||
keys?: string[]
|
||||
}
|
||||
|
||||
class Data {
|
||||
private locale = new PouchDb('local-db', {
|
||||
private locale = new PouchDb('lite-note', {
|
||||
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> {
|
||||
try {
|
||||
const doc = await this.get(id)
|
||||
@@ -57,8 +76,19 @@ class Data {
|
||||
public async getAll<DT extends DataType, T extends Model<DT>>({
|
||||
prefix,
|
||||
includeDocs = true,
|
||||
includeAttachments = false
|
||||
includeAttachments = false,
|
||||
keys = []
|
||||
}: 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({
|
||||
include_docs: includeDocs,
|
||||
attachments: includeAttachments,
|
||||
@@ -69,7 +99,11 @@ class Data {
|
||||
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()}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Octokit } from '@octokit/rest'
|
||||
import { RepoBase } from '@/modules/interfaces/RepoBase'
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
|
||||
|
||||
export const useRepos = () => {
|
||||
const { username, accessToken } = useGitHubLogin()
|
||||
const repos = useAsyncState(async () => {
|
||||
const repos = useAsyncState<RepoBase[]>(async () => {
|
||||
if (!accessToken.value || !username.value) {
|
||||
return []
|
||||
}
|
||||
@@ -18,7 +19,11 @@ export const useRepos = () => {
|
||||
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 {
|
||||
|
||||
5
src/modules/interfaces/RepoBase.ts
Normal file
5
src/modules/interfaces/RepoBase.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface RepoBase {
|
||||
id: string
|
||||
name: string
|
||||
isPrivate: boolean
|
||||
}
|
||||
8
src/modules/models/FavoriteRepo.ts
Normal file
8
src/modules/models/FavoriteRepo.ts
Normal 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
|
||||
}
|
||||
47
src/modules/repo/hooks/useFavoriteRepos.hook.ts
Normal file
47
src/modules/repo/hooks/useFavoriteRepos.hook.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
42
src/modules/repo/hooks/userRepoList.hook.ts
Normal file
42
src/modules/repo/hooks/userRepoList.hook.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
@charset "utf-8";
|
||||
@import url('https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap');
|
||||
|
||||
$primary: #2C3A47;
|
||||
$link: #58B19F;
|
||||
$primary: #2c3a47;
|
||||
$link: #58b19f;
|
||||
|
||||
@import '~bulma/bulma.sass';
|
||||
|
||||
|
||||
@@ -1,32 +1,88 @@
|
||||
<template>
|
||||
<div class="repo-list">
|
||||
<h1>Repositories</h1>
|
||||
<go-back />
|
||||
<h1 class="title is-1">Repositories</h1>
|
||||
<span v-if="!isReady">loading...</span>
|
||||
<ul>
|
||||
<li v-for="repo in repos" :key="repo">
|
||||
<router-link
|
||||
:to="{ name: 'Home', params: { user: username, repo: repo } }"
|
||||
>
|
||||
{{ repo }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="columns is-centered">
|
||||
<div class="column is-one-third">
|
||||
<table class="table is-striped is-hoverable is-fullwidth">
|
||||
<tr v-for="repo in favoriteRepos" :key="repo.id">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="favorites"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { useRepos } from '@/hooks/useRepos.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({
|
||||
name: 'RepoList',
|
||||
components: { GoBack },
|
||||
setup() {
|
||||
const { username } = useGitHubLogin()
|
||||
const { isReady } = useRepos()
|
||||
const {
|
||||
favoriteRepos,
|
||||
otherRepos,
|
||||
favoriteCheckboxes,
|
||||
toggleCheckbox
|
||||
} = useRepoList()
|
||||
|
||||
return {
|
||||
...useRepos(),
|
||||
username
|
||||
isReady,
|
||||
username,
|
||||
favoriteRepos,
|
||||
otherRepos,
|
||||
favoriteCheckboxes,
|
||||
toggleCheckbox
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -35,6 +91,9 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.repo-list {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user