(cards) implements spaced repetition cards

This commit is contained in:
2021-06-12 16:11:47 +02:00
parent 54bce75122
commit 257dc0794b
9 changed files with 210 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
<template> <template>
<main class="flux-note content note-container"> <main class="flux-note repo-note content note-container">
<div class="note readme"> <div class="note readme">
<header-note v-if="withHeader" class="header" :user="user" :repo="repo" /> <header-note v-if="withHeader" class="header" :user="user" :repo="repo" />
<div class="repo-title-breadcrumb"> <div class="repo-title-breadcrumb">
@@ -26,7 +26,11 @@
/> />
</div> </div>
<div v-else-if="!hasContent">No content here 📝</div> <div v-else-if="!hasContent">No content here 📝</div>
<p class="note-display" v-html="renderedContent"></p> <p
v-else-if="withContent"
class="note-display"
v-html="renderedContent"
></p>
</div> </div>
<stacked-note <stacked-note
v-for="(stackedNote, index) in stackedNotes" v-for="(stackedNote, index) in stackedNotes"
@@ -74,6 +78,7 @@ export default defineComponent({
repo: { type: String, required: true }, repo: { type: String, required: true },
content: { type: String, required: false, default: null }, content: { type: String, required: false, default: null },
parseContent: { type: Boolean, required: false, default: true }, parseContent: { type: Boolean, required: false, default: true },
withContent: { type: Boolean, required: false, default: true },
withHeader: { type: Boolean, required: false, default: true } withHeader: { type: Boolean, required: false, default: true }
}, },
setup(props) { setup(props) {
@@ -138,12 +143,6 @@ export default defineComponent({
$header-height: 40px; $header-height: 40px;
.flux-note { .flux-note {
font-family: var(--font-family);
color: var(--font-color);
background-color: var(--background-color);
transition-property: color, background-color;
transition: cubic-bezier(0.39, 0.575, 0.565, 1) 0.2s;
display: flex; display: flex;
flex: 1; flex: 1;

View File

@@ -6,6 +6,12 @@
> >
<img src="@/assets/icons/dark-left-arrow.svg" alt="go back left arrow" /> <img src="@/assets/icons/dark-left-arrow.svg" alt="go back left arrow" />
</router-link> </router-link>
<router-link
class="special-folder"
:to="{ name: 'SpacedRepetitionCard', params: { user, repo } }"
>
cards
</router-link>
<router-link <router-link
class="special-folder" class="special-folder"
:to="{ name: 'DraftNotes', params: { user, repo } }" :to="{ name: 'DraftNotes', params: { user, repo } }"

View File

@@ -11,6 +11,17 @@ export const useFile = (sha: string, retrieveContent = true) => {
const fromCache = ref(false) const fromCache = ref(false)
const content = ref('') const content = ref('')
const rawContent = ref('')
const getRawContent = async () => {
const fileContent = await getCachedFileContent()
if (!fileContent) {
return
}
content.value = render(fileContent)
rawContent.value = fileContent
return rawContent.value
}
const getContent = async () => { const getContent = async () => {
const fileContent = await getCachedFileContent() const fileContent = await getCachedFileContent()
@@ -18,6 +29,7 @@ export const useFile = (sha: string, retrieveContent = true) => {
return return
} }
content.value = render(fileContent) content.value = render(fileContent)
rawContent.value = fileContent
return content.value return content.value
} }
@@ -45,6 +57,7 @@ export const useFile = (sha: string, retrieveContent = true) => {
return { return {
content, content,
getRawContent,
getContent, getContent,
getCachedFileContent, getCachedFileContent,
fromCache fromCache

View File

@@ -8,6 +8,7 @@ export const useLinks = (className: string, sha?: string) => {
const linkNote: EventListener = (event) => { const linkNote: EventListener = (event) => {
event.preventDefault() event.preventDefault()
event.stopPropagation()
const target = event.target as HTMLElement const target = event.target as HTMLElement
const href = target.getAttribute('href') const href = target.getAttribute('href')

View File

@@ -0,0 +1,51 @@
import { useFile } from '@/hooks/useFile.hook'
import { useLinks } from '@/hooks/useLinks.hook'
import { useMarkdown } from '@/hooks/useMarkdown.hook'
import { Card } from '@/modules/card/models/Card'
import { useUserRepoStore } from '@/modules/repo/store/userRepo.store'
import { asyncComputed } from '@vueuse/core'
import { computed, nextTick, watch } from 'vue'
export const useSpacedRepetitionCards = () => {
const { renderString } = useMarkdown()
const store = useUserRepoStore()
const { listenToClick } = useLinks('flip-card')
const cardFiles = computed(() =>
store.files.filter(
(file) => file.path?.startsWith('_cards') && file.path?.endsWith('.md')
)
)
const cards = asyncComputed(async () => {
const cards: Card[] = []
for (const cardFile of cardFiles.value) {
if (!cardFile.sha) {
continue
}
const { getRawContent } = useFile(cardFile.sha, false)
const content = await getRawContent()
const [front, back, references] =
decodeURIComponent(escape(atob(content ?? '')))?.split('___') ?? []
cards.push({
front: renderString(front),
back: renderString(back),
references: renderString(references)
})
}
return cards
}, [])
watch(
cards,
() =>
nextTick(() => {
listenToClick()
}),
{ immediate: true }
)
return { cards }
}

View File

@@ -0,0 +1,5 @@
export interface Card {
front: string
back: string
references: string
}

View File

@@ -41,6 +41,15 @@ const routes: Array<RouteRecordRaw> = [
component: () => component: () =>
import(/* webpackChunkName: "draft-notes" */ '@/views/DraftNotes.vue') import(/* webpackChunkName: "draft-notes" */ '@/views/DraftNotes.vue')
}, },
{
path: '/:user/:repo/spaced-repetition',
name: 'SpacedRepetitionCard',
props: true,
component: () =>
import(
/* webpackChunkName: "spaced-repetition-card" */ '@/views/SpacedRepetitionCard.vue'
)
},
{ {
path: '/about', path: '/about',
name: 'About', name: 'About',

View File

@@ -80,6 +80,14 @@ a {
background-color: $link; background-color: $link;
} }
.repo-note {
font-family: var(--font-family);
color: var(--font-color);
background-color: var(--background-color);
transition-property: color, background-color;
transition: cubic-bezier(0.39, 0.575, 0.565, 1) 0.2s;
}
@media print { @media print {
html, html,
body { body {

View File

@@ -0,0 +1,110 @@
<template>
<div class="spaced-repetition-card repo-note">
<flux-note
key="spaced-repetition-card"
:user="user"
:repo="repo"
:with-content="false"
>
<div
v-for="(card, i) in cards"
:key="i"
class="flip-card"
:class="{ flipped }"
@click="flip"
>
<div class="flip-card-inner">
<div
class="flip-card-front flip-card-content"
v-html="card.front"
></div>
<div class="flip-card-back flip-card-content">
<div class="back" v-html="card.back"></div>
<hr />
<div class="references" v-html="card.references"></div>
</div>
</div>
</div>
</flux-note>
</div>
</template>
<script lang="ts">
import { useSpacedRepetitionCards } from '@/modules/card/hooks/useSpacedRepetitionCards'
import { defineComponent, ref } from 'vue'
import FluxNote from '@/components/FluxNote.vue'
export default defineComponent({
name: 'SpacedRepetitionCard',
components: {
FluxNote
},
props: {
user: { type: String, required: true },
repo: { type: String, required: true }
},
setup() {
const { cards } = useSpacedRepetitionCards()
const flipped = ref(false)
const flip = () => (flipped.value = !flipped.value)
return {
cards,
flipped,
flip
}
}
})
</script>
<style scoped lang="scss">
.spaced-repetition-card {
display: flex;
flex: 1;
padding: 0 1rem;
.flip-card {
background-color: transparent;
width: 400px;
height: 200px;
margin: auto;
&:hover {
cursor: pointer;
}
}
.flip-card-inner {
position: relative;
width: 100%;
height: 100%;
transition: cubic-bezier(0.39, 0.575, 0.565, 1) 0.4s;
transform-style: preserve-3d;
border-radius: 1rem;
}
.flip-card.flipped .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front,
.flip-card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
}
.flip-card-content {
background-color: #ebebeb;
color: var(--font-color);
padding: 1rem;
border-radius: 1rem;
}
.flip-card-back {
transform: rotateY(180deg);
}
}
</style>