✨ (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>
|
<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 {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export enum DataType {
|
export enum DataType {
|
||||||
GithubAccessToken = 'GithubAccessToken'
|
GithubAccessToken = 'GithubAccessToken',
|
||||||
|
FavoriteRepo = 'FavoriteRepo'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
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";
|
@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';
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user