Merge branch 'main' of github.com:lite-note/lite-note into main

This commit is contained in:
2021-05-05 23:53:57 +02:00
16 changed files with 326 additions and 75 deletions

View File

@@ -16,6 +16,7 @@
"@vueuse/core": "^4.4.0", "@vueuse/core": "^4.4.0",
"bulma": "^0.9.2", "bulma": "^0.9.2",
"core-js": "^3.9.0", "core-js": "^3.9.0",
"date-fns": "^2.21.1",
"markdown-it": "^12.0.4", "markdown-it": "^12.0.4",
"markdown-it-block-embed": "^0.0.3", "markdown-it-block-embed": "^0.0.3",
"nanoid": "^3.1.22", "nanoid": "^3.1.22",

View File

@@ -0,0 +1,44 @@
<template>
<div class="authorize">
<div v-if="hasError">An error occured when sign in...</div>
</div>
</template>
<script lang="ts">
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { defineComponent, onBeforeMount, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { signIn } from '@/modules/user/service/signIn'
export default defineComponent({
name: 'Authorize',
setup() {
const route = useRoute()
const router = useRouter()
const { saveCredentials } = useGitHubLogin()
const code = route.query.code
let hasError = ref(false)
onBeforeMount(async () => {
if (code) {
const body = await signIn(code.toString())
if ('error' in body) {
hasError.value = true
} else {
body.access_token
saveCredentials(body)
}
router.push({ name: 'Home' })
}
})
return {
code,
hasError
}
}
})
</script>

View File

@@ -0,0 +1,41 @@
<template>
<a :href="url" class="sign-in-github button is-primary">
<span>
Sign in with
<img src="@/assets/icons/github.svg" alt="GitHub" />
</span>
</a>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
const GITHUB_URL = 'https://github.com/login/oauth/authorize'
const CLIENT_ID = 'Iv1.12dc43d013ce3623'
const SCOPE = 'repo'
export default defineComponent({
name: 'SignInGitHub',
setup() {
const url = new URL(GITHUB_URL)
url.searchParams.set('client_id', CLIENT_ID)
url.searchParams.set('scope', SCOPE)
return {
url
}
}
})
</script>
<style scoped lang="scss">
.sign-in-github {
span {
display: flex;
align-items: flex-end;
gap: 0.5rem;
}
}
</style>

View File

@@ -3,6 +3,7 @@
<div class="columns is-vcentered"> <div class="columns is-vcentered">
<div class="column get-started"> <div class="column get-started">
<h3 class="title is-3">Lite Note</h3> <h3 class="title is-3">Lite Note</h3>
<div class="buttons is-centered">
<router-link <router-link
:to="{ :to="{
name: 'Home', name: 'Home',
@@ -11,6 +12,11 @@
class="button is-primary" class="button is-primary"
>Get started</router-link >Get started</router-link
> >
<router-link class="button" :to="{ name: 'About' }"
>about</router-link
>
</div>
<sign-in-github class="github-login" />
</div> </div>
<div class="column"> <div class="column">
<p> <p>
@@ -90,8 +96,6 @@
rel="noopener noreferrer" rel="noopener noreferrer"
>Julien</a >Julien</a
> >
|
<router-link :to="{ name: 'About' }">about</router-link>
</p> </p>
</footer> </footer>
</div> </div>
@@ -102,8 +106,10 @@ 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' import { useFavoriteRepos } from '@/modules/repo/hooks/useFavoriteRepos.hook'
import SignInGithub from '@/components/SignInGithub.vue'
export default defineComponent({ export default defineComponent({
components: { SignInGithub },
name: 'WelcomeWord', name: 'WelcomeWord',
setup() { setup() {
const { isLogged, username } = useGitHubLogin() const { isLogged, username } = useGitHubLogin()
@@ -119,7 +125,9 @@ export default defineComponent({
padding: 1rem; padding: 1rem;
margin: auto; margin: auto;
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
justify-content: space-between;
.get-started { .get-started {
margin: center; margin: center;
@@ -130,6 +138,10 @@ export default defineComponent({
h4 { h4 {
text-align: center; text-align: center;
} }
.github-login {
margin-top: 1rem;
}
} }
footer { footer {

View File

@@ -3,5 +3,10 @@ import { Model } from '@/data/models/Model'
export interface GithubAccessToken extends Model<DataType.GithubAccessToken> { export interface GithubAccessToken extends Model<DataType.GithubAccessToken> {
username: string username: string
personalAccessToken: string token: string
expirationDate: string
expiresIn: number
refreshToken: string
refreshTokenExpiresIn: number
refreshTokenExpirationDate: string
} }

View File

@@ -1,51 +1,35 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { DataType } from '@/data/DataType.enum'
import { GithubAccessToken } from '@/data/models/GithubAccessToken'
import { data } from '@/data/data'
import { confirmMessage } from '@/utils/notif' import { confirmMessage } from '@/utils/notif'
import { GithubToken } from '@/modules/user/interfaces/GithubToken'
import { getAccessToken, saveAccessToken } from '@/modules/user/service/signIn'
const personalAccessTokenId = 'PAT'
const username = ref<string | null>(null) const username = ref<string | null>(null)
const accessToken = ref<string | null>(null) const accessToken = ref<string | null>(null)
let init = true let init = true
export const useGitHubLogin = () => { export const useGitHubLogin = () => {
const getAccessToken = async () => { const saveAccessTokenToLocal = async () => {
const response = await data.get< const response = await getAccessToken()
DataType.GithubAccessToken,
GithubAccessToken
>(data.generateId(DataType.GithubAccessToken, personalAccessTokenId))
username.value = response?.username || '' username.value = response?.username || ''
accessToken.value = response?.personalAccessToken || '' accessToken.value = response?.token || ''
return response
} }
if (init) { if (init) {
init = false init = false
getAccessToken() saveAccessTokenToLocal()
} }
const saveCredentials = async (username: string, token: string) => { const saveCredentials = async (githubToken: GithubToken) => {
const actualPAT = await getAccessToken() const accessToken = await saveAccessToken(githubToken)
const personalAccessToken: GithubAccessToken = { await saveAccessTokenToLocal()
...actualPAT, confirmMessage(`${accessToken.username} is logged in!`)
_id: data.generateId(DataType.GithubAccessToken, personalAccessTokenId),
$type: DataType.GithubAccessToken,
username,
personalAccessToken: token
}
await data.add(personalAccessToken)
getAccessToken()
confirmMessage('token saved!')
} }
return { return {
isLogged: !!username.value && !!accessToken.value, isLogged: !!accessToken.value,
isReady: computed(() => accessToken.value !== null), isReady: computed(() => accessToken.value !== null),
username, username,
accessToken, accessToken,

View File

@@ -1,7 +1,7 @@
import { Octokit } from '@octokit/rest'
import { RepoBase } from '@/modules/repo/interfaces/RepoBase' import { RepoBase } from '@/modules/repo/interfaces/RepoBase'
import { useAsyncState } from '@vueuse/core' import { useAsyncState } from '@vueuse/core'
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook' import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { getOctokit } from '@/modules/repo/services/octo'
export const useRepos = () => { export const useRepos = () => {
const { username, accessToken } = useGitHubLogin() const { username, accessToken } = useGitHubLogin()
@@ -10,9 +10,7 @@ export const useRepos = () => {
return [] return []
} }
const octokit = new Octokit({ const octokit = await getOctokit()
auth: accessToken.value
})
const repoList = await octokit.request('GET /search/repositories', { const repoList = await octokit.request('GET /search/repositories', {
q: `user:${username.value}`, q: `user:${username.value}`,

View File

@@ -0,0 +1,37 @@
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { needToRefreshToken, refreshToken } from '@/modules/user/service/signIn'
import { Octokit } from '@octokit/rest'
let refreshingToken = false
let octokit = new Octokit()
const sleep = async (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms))
}
export const getOctokit = async (): Promise<Octokit> => {
const { accessToken } = useGitHubLogin()
octokit = new Octokit({
auth: accessToken.value
})
if (refreshingToken) {
await sleep(100)
return getOctokit()
}
if (!refreshingToken) {
refreshingToken = true
if (await needToRefreshToken()) {
const accessToken = await refreshToken()
if (accessToken) {
octokit = new Octokit({
auth: accessToken?.token
})
}
}
refreshingToken = false
}
return octokit
}

View File

@@ -1,9 +1,8 @@
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { useMarkdown } from '@/hooks/useMarkdown.hook' import { useMarkdown } from '@/hooks/useMarkdown.hook'
import { useNoteCache } from '@/modules/note/hooks/useNoteCache' import { useNoteCache } from '@/modules/note/hooks/useNoteCache'
import { RepoFile } from '@/modules/repo/interfaces/RepoFile' import { RepoFile } from '@/modules/repo/interfaces/RepoFile'
import { UserSettings } from '@/modules/repo/interfaces/UserSettings' import { UserSettings } from '@/modules/repo/interfaces/UserSettings'
import { Octokit } from '@octokit/rest' import { getOctokit } from '@/modules/repo/services/octo'
export const getFiles = async ( export const getFiles = async (
owner: string, owner: string,
@@ -12,12 +11,7 @@ export const getFiles = async (
if (!owner || !repo) { if (!owner || !repo) {
return [] return []
} }
const octokit = await getOctokit()
const { accessToken } = useGitHubLogin()
const octokit = new Octokit({
auth: accessToken.value
})
const commits = await octokit.request('GET /repos/{owner}/{repo}/commits', { const commits = await octokit.request('GET /repos/{owner}/{repo}/commits', {
owner, owner,
@@ -47,16 +41,14 @@ export const getMainReadme = async (owner: string, repo: string) => {
if (!owner || !repo) { if (!owner || !repo) {
return null return null
} }
const { render } = useMarkdown() const { render } = useMarkdown()
const { getCachedNote, saveCacheNote } = useNoteCache('README') const { getCachedNote, saveCacheNote } = useNoteCache('README')
const cachedReadme = await getCachedNote() const cachedReadme = await getCachedNote()
try { try {
const { accessToken } = useGitHubLogin() const octokit = await getOctokit()
const octokit = new Octokit({
auth: accessToken.value
})
const README = await octokit.repos.getReadme({ const README = await octokit.repos.getReadme({
owner, owner,
@@ -101,11 +93,7 @@ export const getFileContent = async (
repo: string, repo: string,
sha: string sha: string
) => { ) => {
const { accessToken } = useGitHubLogin() const octokit = await getOctokit()
const octokit = new Octokit({
auth: accessToken.value
})
if (!user || !repo) { if (!user || !repo) {
null null

View File

@@ -28,10 +28,8 @@ export const useUserRepoStore = defineStore({
async setUserRepo(newUser: string, newRepo: string) { async setUserRepo(newUser: string, newRepo: string) {
this.user = newUser this.user = newUser
this.repo = newRepo this.repo = newRepo
const [readme, files] = await Promise.all([ const readme = await getMainReadme(newUser, newRepo)
getMainReadme(newUser, newRepo), const files = await getFiles(newUser, newRepo)
getFiles(newUser, newRepo)
])
this.userSettings = await getUserSettingsContent(newUser, newRepo, files) this.userSettings = await getUserSettingsContent(newUser, newRepo, files)
this.readme = readme this.readme = readme

View File

@@ -0,0 +1,8 @@
export interface GithubToken {
access_token: string
expires_in: number
refresh_token: string
refresh_token_expires_in: number
scope: string
token_type: 'bearer'
}

View File

@@ -0,0 +1,5 @@
export interface GithubTokenError {
error: string
error_description: string
error_uri: string
}

View File

@@ -0,0 +1,118 @@
import { data } from '@/data/data'
import { DataType } from '@/data/DataType.enum'
import { GithubAccessToken } from '@/data/models/GithubAccessToken'
import { GithubToken } from '@/modules/user/interfaces/GithubToken'
import { GithubTokenError } from '@/modules/user/interfaces/GithubTokenError'
import { Octokit } from '@octokit/rest'
import { addSeconds } from 'date-fns'
const AUTHENTICATION_SERVER = 'https://litenote.li212.fr'
const personalTokenId = 'token'
export const signIn = async (
code: string
): Promise<GithubToken | GithubTokenError> => {
const authenticationServerURL = new URL(AUTHENTICATION_SERVER)
authenticationServerURL.searchParams.set('code', code)
const response = await fetch(authenticationServerURL.toString())
const body = (await response.json()) as GithubToken | GithubTokenError
return body
}
export const needToRefreshToken = async () => {
const accessToken = await data.get<
DataType.GithubAccessToken,
GithubAccessToken
>(data.generateId(DataType.GithubAccessToken, personalTokenId))
if (!accessToken) {
return false
}
return new Date(accessToken.expirationDate) <= new Date()
}
export const refreshToken = async () => {
const accessToken = await data.get<
DataType.GithubAccessToken,
GithubAccessToken
>(data.generateId(DataType.GithubAccessToken, personalTokenId))
if (!accessToken) {
return null
}
console.log(accessToken.refreshToken)
if (await needToRefreshToken()) {
const authenticationServerURL = new URL(AUTHENTICATION_SERVER)
authenticationServerURL.searchParams.set('type', 'refresh')
authenticationServerURL.searchParams.set('code', accessToken.refreshToken)
const response = await fetch(authenticationServerURL.toString())
const githubToken = (await response.json()) as
| GithubToken
| GithubTokenError
console.log(githubToken)
if ('error' in githubToken) {
return null
}
return await saveAccessToken(githubToken)
}
return accessToken
}
export const getAccessToken = async () => {
const response = await data.get<
DataType.GithubAccessToken,
GithubAccessToken
>(data.generateId(DataType.GithubAccessToken, personalTokenId))
return response
}
export const saveAccessToken = async (githubToken: GithubToken) => {
const actualPAT = await getAccessToken()
const expirationDate = addSeconds(
new Date(),
githubToken.expires_in
).toISOString()
const refreshTokenExpirationDate = addSeconds(
new Date(),
githubToken.refresh_token_expires_in
).toISOString()
const accessToken: GithubAccessToken = {
...actualPAT,
_id: data.generateId(DataType.GithubAccessToken, personalTokenId),
$type: DataType.GithubAccessToken,
token: githubToken.access_token,
expiresIn: githubToken.expires_in,
expirationDate,
refreshToken: githubToken.refresh_token,
refreshTokenExpiresIn: githubToken.refresh_token_expires_in,
refreshTokenExpirationDate,
username: ''
}
console.log(accessToken)
const octokit = new Octokit({
auth: accessToken?.token
})
const user = await octokit.request('GET /user')
accessToken.username = user.data.login
await data.add(accessToken)
return accessToken
}

View File

@@ -1,24 +1,22 @@
<template> <template>
<div class="about content"> <div class="about content">
<h1 class="title is-1">Lite Note</h1>
<go-back /> <go-back />
<hr />
<main> <main>
Work in progress: Work in progress:
<ol> <ol>
<li>header note for quick actions </li> <li>header note for quick actions </li>
<li>offline notes</li> <li>offline notes </li>
<li>full path resolver between notes</li> <li>full path resolver between notes </li>
<li>draft & fleeting notes folders</li> <li>draft & fleeting notes folders </li>
<li>login with GitHub "Personal Access Token" tutorial</li> <li>private & public notes tutorial </li>
<li>private & public notes tutorial</li>
<li> <li>
custom settings: custom settings:
<ul> <ul>
<li>light & dark modes</li> <li>light & dark modes </li>
<li>font families</li> <li>font families </li>
</ul> </ul>
</li> </li>
<li>Share PDF section of a note (and its subnotes) </li>
</ol> </ol>
</main> </main>
</div> </div>

View File

@@ -1,5 +1,6 @@
<template> <template>
<div class="home content" v-if="!user || !repo"> <div class="home content" v-if="!user || !repo">
<authorize class="authorize" />
<new-version class="new-version" /> <new-version class="new-version" />
<welcome-world /> <welcome-world />
</div> </div>
@@ -10,6 +11,7 @@
import { defineComponent, defineAsyncComponent, computed } from 'vue' import { defineComponent, defineAsyncComponent, computed } from 'vue'
import { useQueryStackedNotes } from '@/hooks/useQueryStackedNotes.hook' import { useQueryStackedNotes } from '@/hooks/useQueryStackedNotes.hook'
import NewVersion from '@/components/NewVersion.vue' import NewVersion from '@/components/NewVersion.vue'
import Authorize from '@/components/Authorize.vue'
const FluxNote = defineAsyncComponent(() => import('@/components/FluxNote.vue')) const FluxNote = defineAsyncComponent(() => import('@/components/FluxNote.vue'))
@@ -22,7 +24,8 @@ export default defineComponent({
components: { components: {
WelcomeWorld, WelcomeWorld,
FluxNote, FluxNote,
NewVersion NewVersion,
Authorize
}, },
props: { props: {
user: { type: String, required: false, default: '' }, user: { type: String, required: false, default: '' },
@@ -47,7 +50,13 @@ export default defineComponent({
align-items: center; align-items: center;
.new-version { .new-version {
margin-top: 1rem; position: absolute;
top: 1rem;
right: 1rem;
} }
} }
.authorize {
margin: auto;
}
</style> </style>

View File

@@ -4355,6 +4355,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
whatwg-mimetype "^2.2.0" whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0" whatwg-url "^7.0.0"
date-fns@^2.21.1:
version "2.21.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.21.1.tgz#679a4ccaa584c0706ea70b3fa92262ac3009d2b0"
integrity sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA==
deasync@^0.1.15: deasync@^0.1.15:
version "0.1.20" version "0.1.20"
resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.20.tgz#546fd2660688a1eeed55edce2308c5cf7104f9da" resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.20.tgz#546fd2660688a1eeed55edce2308c5cf7104f9da"