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

View File

@@ -3,5 +3,10 @@ import { Model } from '@/data/models/Model'
export interface GithubAccessToken extends Model<DataType.GithubAccessToken> {
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 { DataType } from '@/data/DataType.enum'
import { GithubAccessToken } from '@/data/models/GithubAccessToken'
import { data } from '@/data/data'
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 accessToken = ref<string | null>(null)
let init = true
export const useGitHubLogin = () => {
const getAccessToken = async () => {
const response = await data.get<
DataType.GithubAccessToken,
GithubAccessToken
>(data.generateId(DataType.GithubAccessToken, personalAccessTokenId))
const saveAccessTokenToLocal = async () => {
const response = await getAccessToken()
username.value = response?.username || ''
accessToken.value = response?.personalAccessToken || ''
return response
accessToken.value = response?.token || ''
}
if (init) {
init = false
getAccessToken()
saveAccessTokenToLocal()
}
const saveCredentials = async (username: string, token: string) => {
const actualPAT = await getAccessToken()
const saveCredentials = async (githubToken: GithubToken) => {
const accessToken = await saveAccessToken(githubToken)
const personalAccessToken: GithubAccessToken = {
...actualPAT,
_id: data.generateId(DataType.GithubAccessToken, personalAccessTokenId),
$type: DataType.GithubAccessToken,
username,
personalAccessToken: token
}
await data.add(personalAccessToken)
getAccessToken()
confirmMessage('token saved!')
await saveAccessTokenToLocal()
confirmMessage(`${accessToken.username} is logged in!`)
}
return {
isLogged: !!username.value && !!accessToken.value,
isLogged: !!accessToken.value,
isReady: computed(() => accessToken.value !== null),
username,
accessToken,

View File

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

View File

@@ -28,10 +28,8 @@ export const useUserRepoStore = defineStore({
async setUserRepo(newUser: string, newRepo: string) {
this.user = newUser
this.repo = newRepo
const [readme, files] = await Promise.all([
getMainReadme(newUser, newRepo),
getFiles(newUser, newRepo)
])
const readme = await getMainReadme(newUser, newRepo)
const files = await getFiles(newUser, newRepo)
this.userSettings = await getUserSettingsContent(newUser, newRepo, files)
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>
<div class="about content">
<h1 class="title is-1">Lite Note</h1>
<go-back />
<hr />
<main>
Work in progress:
<ol>
<li>header note for quick actions </li>
<li>offline notes</li>
<li>full path resolver between notes</li>
<li>draft & fleeting notes folders</li>
<li>login with GitHub "Personal Access Token" tutorial</li>
<li>private & public notes tutorial</li>
<li>header note for quick actions </li>
<li>offline notes </li>
<li>full path resolver between notes </li>
<li>draft & fleeting notes folders </li>
<li>private & public notes tutorial </li>
<li>
custom settings:
<ul>
<li>light & dark modes</li>
<li>font families</li>
<li>light & dark modes </li>
<li>font families </li>
</ul>
</li>
<li>Share PDF section of a note (and its subnotes) </li>
</ol>
</main>
</div>

View File

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

View File

@@ -4355,6 +4355,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
whatwg-mimetype "^2.2.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:
version "0.1.20"
resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.20.tgz#546fd2660688a1eeed55edce2308c5cf7104f9da"