✨ (stacked notes) implements overlay
This commit is contained in:
@@ -1,22 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stacked-note" v-html="content"></div>
|
<div
|
||||||
|
class="stacked-note"
|
||||||
|
:class="{ [className]: true, overlay: displayNoteOverlay }"
|
||||||
|
>
|
||||||
|
<div class="title-stacked-note" :class="titleClassName">{{ title }}</div>
|
||||||
|
<section v-html="content"></section>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, nextTick, watch } from 'vue'
|
import { computed, defineComponent, nextTick, watch } from 'vue'
|
||||||
import { useFile } from '@/hooks/useFile.hook'
|
import { useFile } from '@/hooks/useFile.hook'
|
||||||
import { useLinks } from '@/hooks/useLinks.hook'
|
import { useLinks } from '@/hooks/useLinks.hook'
|
||||||
|
import { useNoteOverlay } from '@/hooks/useNoteOverlay.hook'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'StackedNote',
|
name: 'StackedNote',
|
||||||
props: {
|
props: {
|
||||||
|
index: { type: Number, required: true },
|
||||||
user: { type: String, required: true },
|
user: { type: String, required: true },
|
||||||
repo: { type: String, required: true },
|
repo: { type: String, required: true },
|
||||||
|
title: { type: String, required: true },
|
||||||
sha: { type: String, required: true }
|
sha: { type: String, required: true }
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { content } = useFile(props.user, props.repo, props.sha)
|
const { content } = useFile(props.user, props.repo, props.sha)
|
||||||
const { listenToClick } = useLinks('stacked-note', props.sha)
|
const { listenToClick } = useLinks('stacked-note', props.sha)
|
||||||
|
const className = computed(() => `stacked-note-${props.index}`)
|
||||||
|
const titleClassName = computed(() => `title-${className.value}`)
|
||||||
|
|
||||||
|
const { displayNoteOverlay } = useNoteOverlay(className.value, props.index)
|
||||||
|
|
||||||
watch(content, () => {
|
watch(content, () => {
|
||||||
if (content.value) {
|
if (content.value) {
|
||||||
@@ -26,24 +39,43 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { content }
|
return { content, titleClassName, className, displayNoteOverlay }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
$border-color: rgba(18, 19, 58, 0.2);
|
||||||
|
|
||||||
.stacked-note {
|
.stacked-note {
|
||||||
text-align: left;
|
padding: 1rem 3rem;
|
||||||
border-top: 1px solid rgba(18, 19, 58, 0.2);
|
|
||||||
padding: 0 1rem;
|
transition: cubic-bezier(0.39, 0.575, 0.565, 1) 0.3s;
|
||||||
overflow-y: auto;
|
|
||||||
height: 100vh;
|
&.overlay {
|
||||||
|
box-shadow: -3px 0 0.4em $border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-stacked-note {
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
top: 1rem;
|
||||||
|
left: 1.5rem;
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.title-stacked-note {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 769px) {
|
@media screen and (min-width: 769px) {
|
||||||
.stacked-note {
|
.stacked-note {
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
border-left: 1px solid rgba(18, 19, 58, 0.2);
|
border-left: 1px solid $border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Ref, ref } from '@vue/reactivity'
|
import { Ref, ref } from '@vue/reactivity'
|
||||||
import { nextTick, onUnmounted, watch } from '@vue/runtime-core'
|
import { computed, nextTick, onUnmounted, watch } from '@vue/runtime-core'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
import { noteEventBus } from '@/bus/noteBusEvent'
|
import { noteEventBus } from '@/bus/noteBusEvent'
|
||||||
@@ -26,6 +26,19 @@ export const useNote = (user: Ref<string>, repo: Ref<string>) => {
|
|||||||
|
|
||||||
const { readme, notFound, tree } = useRepo(user, repo)
|
const { readme, notFound, tree } = useRepo(user, repo)
|
||||||
const { listenToClick } = useLinks('note-display')
|
const { listenToClick } = useLinks('note-display')
|
||||||
|
const titles = computed(() => {
|
||||||
|
return stackedNotes.value.reduce((obj: Record<string, string>, note) => {
|
||||||
|
if (!note) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
const filePath = tree.value.find((file) => file.sha === note)?.path ?? ''
|
||||||
|
const fileNames = filePath.split('.')
|
||||||
|
fileNames.pop()
|
||||||
|
obj[note] = fileNames.join('.')
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
|
|
||||||
const unsubscribe = noteEventBus.addEventBusListener(
|
const unsubscribe = noteEventBus.addEventBusListener(
|
||||||
({ path, currentNoteSHA }) => {
|
({ path, currentNoteSHA }) => {
|
||||||
@@ -93,6 +106,7 @@ export const useNote = (user: Ref<string>, repo: Ref<string>) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
titles,
|
||||||
readme,
|
readme,
|
||||||
notFound,
|
notFound,
|
||||||
stackedNotes
|
stackedNotes
|
||||||
|
|||||||
27
src/hooks/useNoteOverlay.hook.ts
Normal file
27
src/hooks/useNoteOverlay.hook.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { computed, onMounted } from '@vue/runtime-core'
|
||||||
|
|
||||||
|
import { useOverlay } from '@/hooks/useOverlay.hook'
|
||||||
|
|
||||||
|
const BOOKMARK_WIDTH = 2
|
||||||
|
const NOTE_WIDTH = 620
|
||||||
|
|
||||||
|
export const useNoteOverlay = (className: string, index: number) => {
|
||||||
|
const { x } = useOverlay()
|
||||||
|
const displayNoteOverlay = computed(() => x.value > index * NOTE_WIDTH)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const noteElement = document.querySelector(
|
||||||
|
`.${className}`
|
||||||
|
) as HTMLElement | null
|
||||||
|
|
||||||
|
if (!noteElement) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
noteElement.style.left = `${(index + 1) * BOOKMARK_WIDTH}rem`
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
displayNoteOverlay
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/hooks/useOverlay.hook.ts
Normal file
27
src/hooks/useOverlay.hook.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useEventListener } from '@vueuse/core'
|
||||||
|
|
||||||
|
export const useOverlay = () => {
|
||||||
|
const x = ref(0)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const element = document.querySelector('body')
|
||||||
|
|
||||||
|
useEventListener(
|
||||||
|
element,
|
||||||
|
'scroll',
|
||||||
|
(e) => {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
x.value = target.scrollLeft
|
||||||
|
},
|
||||||
|
{
|
||||||
|
passive: true,
|
||||||
|
capture: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Courier Prime', monospace;
|
font-family: 'Courier Prime', monospace;
|
||||||
text-align: center;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
// width: 5000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 769px) {
|
@media screen and (min-width: 769px) {
|
||||||
|
|||||||
@@ -27,35 +27,37 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="home content" v-else>
|
<div v-else-if="notFound">
|
||||||
<hr v-if="notFound" />
|
<hr />
|
||||||
<div v-if="notFound" class="columns is-centered">
|
<div class="columns is-centered">
|
||||||
<div class="column is-one-third notification is-warning" v-if="notFound">
|
<div class="column is-one-third notification is-warning" v-if="notFound">
|
||||||
Not found.
|
Not found.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-container">
|
</div>
|
||||||
<div class="readme">
|
<div class="home content note-container" v-else>
|
||||||
<h1 class="title is-1">
|
<div class="readme note">
|
||||||
<router-link
|
<h1 class="title is-1">
|
||||||
:to="{ name: 'Home', params: { user, repo } }"
|
<router-link
|
||||||
:key="routeKey"
|
:to="{ name: 'Home', params: { user, repo } }"
|
||||||
>
|
:key="routeKey"
|
||||||
{{ repo }}
|
>
|
||||||
</router-link>
|
{{ repo }}
|
||||||
</h1>
|
</router-link>
|
||||||
<h2 class="subtitle is-2">{{ user }}</h2>
|
</h1>
|
||||||
<p class="note-display" v-html="readme"></p>
|
<h2 class="subtitle is-2">{{ user }}</h2>
|
||||||
</div>
|
<p class="note-display" v-html="readme"></p>
|
||||||
<stacked-note
|
|
||||||
class="note"
|
|
||||||
v-for="stackedNote in stackedNotes"
|
|
||||||
:key="stackedNote"
|
|
||||||
:user="user"
|
|
||||||
:repo="repo"
|
|
||||||
:sha="stackedNote"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<stacked-note
|
||||||
|
class="note"
|
||||||
|
v-for="(stackedNote, index) in stackedNotes"
|
||||||
|
:key="stackedNote"
|
||||||
|
:index="index"
|
||||||
|
:user="user"
|
||||||
|
:repo="repo"
|
||||||
|
:sha="stackedNote"
|
||||||
|
:title="titles[stackedNote]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -79,6 +81,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const refProps = toRefs(props)
|
const refProps = toRefs(props)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...useNote(refProps.user, refProps.repo),
|
...useNote(refProps.user, refProps.repo),
|
||||||
...useForm(),
|
...useForm(),
|
||||||
@@ -91,20 +94,43 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.home {
|
.home {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
width: 100%;
|
||||||
|
|
||||||
@media screen and (min-width: 769px) {
|
.readme {
|
||||||
.readme,
|
position: sticky;
|
||||||
.note {
|
left: 0;
|
||||||
min-width: 620px;
|
padding: 0 2rem 1rem;
|
||||||
max-width: 720px;
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
text-align: left;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100vh;
|
||||||
|
position: sticky;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-top: 1px solid rgba(18, 19, 58, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-container {
|
@media screen and (min-width: 769px) {
|
||||||
flex: 1;
|
.note {
|
||||||
display: flex;
|
min-width: 620px;
|
||||||
overflow-x: auto;
|
max-width: 620px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.home {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.note {
|
||||||
|
position: initial;
|
||||||
|
width: 100vw;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user