Add invitation system, table of contents, actionable dashboard, and settings improvements
Invitation system (Phase 5.7): - invitation_tokens table (migration 0012) with SHA256-hashed tokens, 7-day expiry - Admin CRUD endpoints: POST/GET/DELETE /api/admin/invitations - Branded invitation email with registration link - /register-invite frontend view with token validation and account creation - Admin UI: invite form + pending invitations table with revoke - Configurable base URL setting for email links (replaces hardcoded Config.BASE_URL) Table of contents + markdown improvements (Phase 5.8): - TableOfContents component: sticky sidebar, heading parsing, smooth-scroll - Custom marked renderer adds id attributes to headings for anchor links - stripFirstLineTags() prevents leading #tags from rendering as headings - NoteViewerView/TaskViewerView flex layout with TOC sidebar (hidden ≤1200px) - Model catalog refresh: added llama3.2/3.3, gemma3, qwen3, phi4, deepseek-r1, qwen2.5-coder, dolphin3; added Reasoning category; removed discontinued models - Settings model section split into Installed/Available tabs Actionable dashboard (Phase 5.9): - due_before/due_after query params on /api/tasks (exclusive < / inclusive >=) - HomeView rewritten: overdue (red accent), due today, in progress, chats, notes - 5 parallel API calls via Promise.allSettled - Client-side done filtering and in-progress deduplication - Task sections hidden when empty; status toggle removes done tasks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { slugify } from "@/utils/markdown";
|
||||
|
||||
const props = defineProps<{ body: string }>();
|
||||
|
||||
interface TocEntry {
|
||||
depth: number;
|
||||
text: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const headings = computed<TocEntry[]>(() => {
|
||||
const entries: TocEntry[] = [];
|
||||
const re = /^(#{1,6})\s+(.+)$/gm;
|
||||
let match;
|
||||
while ((match = re.exec(props.body)) !== null) {
|
||||
entries.push({
|
||||
depth: match[1].length,
|
||||
text: match[2].trim(),
|
||||
id: slugify(match[2].trim()),
|
||||
});
|
||||
}
|
||||
return entries;
|
||||
});
|
||||
|
||||
function scrollTo(id: string) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav v-if="headings.length" class="toc">
|
||||
<h3 class="toc-title">Contents</h3>
|
||||
<ul class="toc-list">
|
||||
<li
|
||||
v-for="(h, i) in headings"
|
||||
:key="i"
|
||||
:style="{ paddingLeft: (h.depth - 1) * 0.75 + 'rem' }"
|
||||
>
|
||||
<a class="toc-link" @click.prevent="scrollTo(h.id)" :href="`#${h.id}`">
|
||||
{{ h.text }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toc {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.toc-title {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-muted);
|
||||
margin: 0 0 0.5rem;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.toc-list li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.toc-link {
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toc-link:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user