diff --git a/src/App.vue b/src/App.vue index 2cc7eff..ca9f85b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,9 +1,9 @@ diff --git a/src/bus/noteBusEvent.ts b/src/bus/noteBusEvent.ts new file mode 100644 index 0000000..832efd2 --- /dev/null +++ b/src/bus/noteBusEvent.ts @@ -0,0 +1,8 @@ +import { createEventBus } from 'retrobus' + +interface EventBusParams { + path: string + currentNoteSHA?: string +} + +export const noteEventBus = createEventBus('note') diff --git a/src/components/StackedNote.vue b/src/components/StackedNote.vue new file mode 100644 index 0000000..02613c0 --- /dev/null +++ b/src/components/StackedNote.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/hooks/useFile.hook.ts b/src/hooks/useFile.hook.ts new file mode 100644 index 0000000..0a70155 --- /dev/null +++ b/src/hooks/useFile.hook.ts @@ -0,0 +1,30 @@ +import { ref } from 'vue' +import { request } from '@octokit/request' +import { useMarkdown } from '@/hooks/useMarkdown.hook' + +export const useFile = (owner: string, repo: string, sha: string) => { + const content = ref('') + + const getContent = async () => { + const { render } = useMarkdown() + const file = await request( + 'GET /repos/{owner}/{repo}/git/blobs/{file_sha}', + { + owner, + repo, + file_sha: sha + } + ) + + if (!file) { + return + } + content.value = render(file.data.content) + } + + getContent() + + return { + content + } +} diff --git a/src/hooks/useLinks.hook.ts b/src/hooks/useLinks.hook.ts index 502977f..1bc1c7c 100644 --- a/src/hooks/useLinks.hook.ts +++ b/src/hooks/useLinks.hook.ts @@ -1,7 +1,27 @@ -export const useLinks = (className: string) => { - const linkNote: EventListenerOrEventListenerObject = (e) => { - e.preventDefault() - console.log('use links') +import { noteEventBus } from '@/bus/noteBusEvent' +import { onUnmounted } from '@vue/runtime-core' + +const LINKS = ['http://', 'https://'] + +export const useLinks = (className: string, sha?: string) => { + const linkNote: EventListener = (event) => { + event.preventDefault() + const target = event.target as HTMLElement + const href = target.getAttribute('href') + + if (!href) { + return + } + + if (LINKS.some((link) => href.startsWith(link))) { + window.open(href, '_blank') + return + } + + noteEventBus.emit({ + path: href, + currentNoteSHA: sha + }) } const selector = `.${className} a` @@ -21,8 +41,11 @@ export const useLinks = (className: string) => { }) } + onUnmounted(() => { + removeListeners() + }) + return { - listenToClick, - removeListeners + listenToClick } } diff --git a/src/hooks/useMarkdown.hook.ts b/src/hooks/useMarkdown.hook.ts new file mode 100644 index 0000000..343177a --- /dev/null +++ b/src/hooks/useMarkdown.hook.ts @@ -0,0 +1,20 @@ +import MarkdownIt from 'markdown-it' +import markdownItClass from '@toycode/markdown-it-class' +// import sanitizeHtml from 'sanitize-html' + +const md = new MarkdownIt().use(markdownItClass, { + h1: ['title', 'is-1'], + h2: ['subtitle', 'is-2'], + h3: ['subtitle', 'is-3'], + h4: ['subtitle', 'is-4'], + h5: ['subtitle', 'is-5'], + h6: ['subtitle', 'is-6'], + table: ['table', 'is-striped', 'is-hoverable', 'is-fullwidth'] +}) + +export const useMarkdown = () => { + return { + render: (content: string) => + md.render(decodeURIComponent(escape(atob(content)))) + } +} diff --git a/src/hooks/useNote.hook.ts b/src/hooks/useNote.hook.ts new file mode 100644 index 0000000..27fd9a5 --- /dev/null +++ b/src/hooks/useNote.hook.ts @@ -0,0 +1,98 @@ +import { nextTick, onUnmounted, watch } from '@vue/runtime-core' +import { useRoute, useRouter } from 'vue-router' + +import { noteEventBus } from '@/bus/noteBusEvent' +import { ref } from '@vue/reactivity' +import { useLinks } from '@/hooks/useLinks.hook' +import { useRepo } from '@/hooks/useRepo.hook' + +const sanitizePath = (path: string) => { + if (path.startsWith('./')) { + return decodeURIComponent(path.replace('./', '')) + } + return decodeURIComponent(path) +} + +export const useNote = (user: string, repo: string) => { + const { push } = useRouter() + const { readme, notFound, tree } = useRepo(user, repo) + const { listenToClick } = useLinks('note-display') + const { query } = useRoute() + + const stackedNotes = ref( + query.stackedNotes + ? Array.isArray(query.stackedNotes) + ? query.stackedNotes + : [query.stackedNotes] + : [] + ) + + const unsubscribe = noteEventBus.addEventBusListener( + ({ path, currentNoteSHA }) => { + const currentFile = tree.value.find((file) => file.sha === currentNoteSHA) + + const absolutePathArray = currentFile?.path?.split('/') ?? [] + absolutePathArray?.pop() + const absolutePath = absolutePathArray.join('/') + + const finalPath = absolutePath + ? `${sanitizePath(absolutePath)}/${sanitizePath(path)}` + : sanitizePath(path) + + const file = tree.value.find((file) => file.path === finalPath) + + if (!file?.sha || stackedNotes.value.includes(file.sha)) { + return + } + + const getStackedNotes = () => { + if (!file?.sha) { + return [] + } + + if (!currentNoteSHA) { + return [file.sha] + } + + const [splittedStackedNotes] = stackedNotes.value + .join(';') + .split(currentNoteSHA) + + return [ + ...splittedStackedNotes.replaceAll(';;', ';').split(';'), + currentNoteSHA, + file.sha + ].filter((sha) => !!sha) + } + + const newStackedNotes = getStackedNotes() + + push({ + name: 'Note', + query: { + stackedNotes: newStackedNotes + } + }) + + stackedNotes.value = newStackedNotes + } + ) + + watch(readme, () => { + if (readme.value) { + nextTick(() => { + listenToClick() + }) + } + }) + + onUnmounted(() => { + unsubscribe() + }) + + return { + readme, + notFound, + stackedNotes + } +} diff --git a/src/hooks/useRepo.hook.ts b/src/hooks/useRepo.hook.ts index 90fabc7..4a82b49 100644 --- a/src/hooks/useRepo.hook.ts +++ b/src/hooks/useRepo.hook.ts @@ -1,50 +1,67 @@ import { onMounted, ref } from '@vue/runtime-core' -import MarkdownIt from 'markdown-it' import { request } from '@octokit/request' +import { useMarkdown } from '@/hooks/useMarkdown.hook' -const md = new MarkdownIt() +interface Tree { + path?: string + mode?: string + type?: string + sha?: string + size?: number + url?: string +} export const useRepo = (owner: string, repo: string) => { + const { render } = useMarkdown() const readme = ref(null) + const notFound = ref(false) + const tree = ref([]) onMounted(async () => { - const README = await request('GET /repos/{owner}/{repo}/readme', { - repo, - owner - }) - - if (README) { - readme.value = md.render( - decodeURIComponent(escape(atob(README.data.content))) - ) - } - - const commits = await request('GET /repos/{owner}/{repo}/commits', { - owner, - repo - }) - - const lastCommit = commits.data.shift() - - if (!lastCommit) { - return - } - - const tree = await request( - 'GET /repos/{owner}/{repo}/git/trees/{tree_sha}', - { - owner, + try { + const README = await request('GET /repos/{owner}/{repo}/readme', { repo, - tree_sha: lastCommit.commit.tree.sha, - recursive: 'true' - } - ) + owner + }) - console.log(tree.data.tree.filter((t) => t.type === 'blob')) + if (README) { + readme.value = render(README.data.content) + } + + const commits = await request('GET /repos/{owner}/{repo}/commits', { + owner, + repo + }) + + const lastCommit = commits.data.shift() + + if (!lastCommit) { + return + } + + const treeResponse = await request( + 'GET /repos/{owner}/{repo}/git/trees/{tree_sha}', + { + owner, + repo, + tree_sha: lastCommit.commit.tree.sha, + recursive: 'true' + } + ) + + if (treeResponse) { + tree.value = treeResponse.data.tree.filter((t) => t.type === 'blob') + console.log(tree.value) + } + } catch (error) { + notFound.value = true + } }) return { - readme + readme, + notFound, + tree } } diff --git a/src/router/router.ts b/src/router/router.ts index d44cbb5..7a9b6a1 100644 --- a/src/router/router.ts +++ b/src/router/router.ts @@ -1,4 +1,5 @@ -import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' +import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router' + import Home from '@/views/Home.vue' const routes: Array = [ @@ -11,6 +12,12 @@ const routes: Array = [ path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue') + }, + { + path: '/note/:user/:repo', + name: 'Note', + props: true, + component: () => import(/* webpackChunkName: "note" */ '@/views/Note.vue') } ] diff --git a/src/styles/app.scss b/src/styles/app.scss index d3f02bb..5c45711 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -1,24 +1,12 @@ @charset "utf-8"; @import '~bulma/bulma.sass'; +html { + overflow-y: auto; +} + html, body { text-align: center; min-height: 100vh; } - -h1 { - font-size: 3rem; -} - -h2 { - font-size: 2.5rem; -} - -h3 { - font-size: 2rem; -} - -h4 { - font-size: 1.5rem; -} diff --git a/src/views/Home.vue b/src/views/Home.vue index c976c60..feb846f 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -5,7 +5,7 @@