Files
FabledScribe/frontend/src/components/ToastNotification.vue
T
bvandeusen ef141f07f8 UI polish pass: word count, slash commands, task lists, graph peek, bulk delete, export
- WordCount component (toggle words/chars vs read time, persisted mode)
- TipTap: TaskList/TaskItem extensions, slash command menu (H1-H3, lists, code, quote, task)
- Markdown serializer: task list → `- [ ]` / `- [x]` roundtrip
- GraphView: slide-in peek panel for note/task nodes (body, tags, linked nodes); tag nodes still navigate
- ChatView: bulk-select conversations with two-click confirm delete + chat retention policy (default 90d)
- NoteEditorView: pill tab bar with animated edit/preview toggle + WordCount in toolbar
- WorkspaceNoteEditor: inline search with tag matching, inline new-note creation, WordCount
- WorkspaceTaskPanel: task body rendered in slide-over + Edit link
- Settings: data export (Markdown ZIP / JSON) via GET /api/export
- Accessibility: skip-to-content link, aria-labels on all icon-only buttons
- Fix: export route used non-existent fabledassistant.database — corrected to async_session()
- Fix: ToolCallRecord.status type now includes "running" (was causing TS build error)
- Dockerfile: upgrade npm to latest before install to suppress major-version notice
- npm audit fix: patched minimatch (ReDoS) and rollup (path traversal) — 0 vulnerabilities

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 23:15:01 -05:00

88 lines
1.7 KiB
Vue

<script setup lang="ts">
import { useToastStore } from "@/stores/toast";
const toastStore = useToastStore();
</script>
<template>
<div class="toast-container">
<transition-group name="toast">
<div
v-for="toast in toastStore.toasts"
:key="toast.id"
class="toast"
:class="`toast--${toast.type}`"
>
<span class="toast-msg">{{ toast.message }}</span>
<button class="toast-close" aria-label="Dismiss notification" @click="toastStore.dismiss(toast.id)">&times;</button>
</div>
</transition-group>
</div>
</template>
<style scoped>
.toast-container {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.toast {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-radius: 6px;
color: #fff;
font-size: 0.9rem;
box-shadow: 0 2px 8px var(--color-shadow);
min-width: 200px;
}
.toast-msg {
flex: 1;
}
.toast-close {
background: none;
border: none;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 1.2rem;
line-height: 1;
padding: 0 0.15rem;
}
.toast-close:hover {
color: #fff;
}
.toast--success {
background: var(--color-toast-success);
}
.toast--error {
background: var(--color-toast-error);
}
.toast--warning {
background: var(--color-warning);
color: #1a1a1a;
}
.toast--warning .toast-close {
color: rgba(0, 0, 0, 0.5);
}
.toast--warning .toast-close:hover {
color: #1a1a1a;
}
.toast-enter-active,
.toast-leave-active {
transition: all 0.3s ease;
}
.toast-enter-from {
opacity: 0;
transform: translateX(100%);
}
.toast-leave-to {
opacity: 0;
transform: translateY(-20px);
}
</style>