Edit and delete flashcards!

This commit is contained in:
2026-06-28 23:37:26 -07:00
parent 6f68049120
commit 4347cb9c91
3 changed files with 203 additions and 2 deletions
@@ -1,20 +1,38 @@
<template> <template>
<v-card class="pa-5"> <v-card class="pa-5 flashcard-card">
<div <div
class="face-content text-h5 text-center" class="face-content text-h5 text-center"
v-html="renderMarkdown(flashcard.front)" v-html="renderMarkdown(flashcard.front)"
/> />
<div class="kebab-wrapper">
<FlashcardKebab
:flashcard="flashcard"
@click.stop
@updated="$emit('updated', $event)"
@deleted="$emit('deleted', $event)"
/>
</div>
</v-card> </v-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { type Flashcard } from "@/api/flashcards-api" import { type Flashcard } from "@/api/flashcards-api"
import renderMarkdown from "@/utils/render-markdown" import renderMarkdown from "@/utils/render-markdown"
import FlashcardKebab from "@/components/flashcards/FlashcardKebab.vue"
defineProps<{ flashcard: Flashcard }>() defineProps<{ flashcard: Flashcard }>()
defineEmits<{
updated: [flashcard: Flashcard]
deleted: [flashcardId: number]
}>()
</script> </script>
<style scoped> <style scoped>
.flashcard-card {
position: relative;
}
.face-content { .face-content {
flex: 1; flex: 1;
display: flex; display: flex;
@@ -24,4 +42,16 @@ defineProps<{ flashcard: Flashcard }>()
line-height: 1.5; line-height: 1.5;
white-space: pre-line; white-space: pre-line;
} }
.kebab-wrapper {
position: absolute;
top: 4px;
right: 4px;
opacity: 0;
transition: opacity 0.15s;
}
.flashcard-card:hover .kebab-wrapper {
opacity: 1;
}
</style> </style>
@@ -0,0 +1,167 @@
<template>
<v-menu>
<template #activator="{ props: menuProps }">
<v-btn
icon
variant="text"
density="comfortable"
size="default"
v-bind="menuProps"
>
<v-icon icon="mdi-dots-vertical" />
</v-btn>
</template>
<v-list density="compact">
<v-list-item
prepend-icon="mdi-pencil-outline"
title="Edit"
@click="openEdit"
/>
<v-list-item
prepend-icon="mdi-delete-outline"
title="Delete"
class="text-error"
@click="showDelete = true"
/>
</v-list>
</v-menu>
<v-dialog
v-model="showEdit"
width="500"
>
<v-form
ref="editFormRef"
@submit.prevent="updateFlashcard"
>
<v-card>
<v-card-title>Edit Flashcard</v-card-title>
<v-card-text class="d-flex flex-column ga-2">
<v-textarea
v-model="editFront"
label="Front"
rows="3"
auto-grow
:rules="[required]"
/>
<v-textarea
v-model="editBack"
label="Back"
rows="3"
auto-grow
:rules="[required]"
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
variant="text"
text="Cancel"
@click="showEdit = false"
/>
<v-btn
type="submit"
color="primary"
variant="flat"
:loading="isSubmitting"
text="Save"
/>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
<v-dialog
v-model="showDelete"
width="400"
>
<v-card>
<v-card-title>Delete Flashcard</v-card-title>
<v-card-text>Are you sure you want to delete this flashcard?</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
variant="text"
text="Cancel"
@click="showDelete = false"
/>
<v-btn
color="error"
variant="flat"
:loading="isSubmitting"
text="Delete"
@click="deleteFlashcard"
/>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { ref } from "vue"
import { VForm } from "vuetify/components"
import { required } from "@/utils/validators"
import flashcardsApi, { type Flashcard } from "@/api/flashcards-api"
import useSnack from "@/use/use-snack"
const props = defineProps<{ flashcard: Flashcard }>()
const emit = defineEmits<{
updated: [flashcard: Flashcard]
deleted: [flashcardId: number]
}>()
const snack = useSnack()
const isSubmitting = ref(false)
// Edit
const showEdit = ref(false)
const editFront = ref("")
const editBack = ref("")
const editFormRef = ref<InstanceType<typeof VForm> | null>(null)
function openEdit() {
editFront.value = props.flashcard.front
editBack.value = props.flashcard.back
showEdit.value = true
}
async function updateFlashcard() {
if (!editFormRef.value) return
const { valid } = await editFormRef.value.validate()
if (!valid) return
try {
isSubmitting.value = true
const { flashcard } = await flashcardsApi.update(props.flashcard.id, {
front: editFront.value,
back: editBack.value,
})
emit("updated", flashcard)
showEdit.value = false
snack.success("Flashcard updated")
} catch {
snack.error("Failed to update flashcard")
} finally {
isSubmitting.value = false
}
}
// Delete
const showDelete = ref(false)
async function deleteFlashcard() {
try {
isSubmitting.value = true
await flashcardsApi.delete(props.flashcard.id)
emit("deleted", props.flashcard.id)
showDelete.value = false
snack.success("Flashcard deleted")
} catch {
snack.error("Failed to delete flashcard")
} finally {
isSubmitting.value = false
}
}
</script>
+5 -1
View File
@@ -43,7 +43,11 @@
md="6" md="6"
lg="4" lg="4"
> >
<FlashcardCard :flashcard="flashcard" /> <FlashcardCard
:flashcard="flashcard"
@updated="refreshFlashcards"
@deleted="refreshFlashcards"
/>
</v-col> </v-col>
</v-row> </v-row>