From 0fc72dbba6c66623dea5e110d06982e0b1af5dde Mon Sep 17 00:00:00 2001 From: erangel1 Date: Thu, 4 Jun 2026 20:41:37 +0200 Subject: [PATCH] fixed issues with app --- convex/analytics.ts | 29 +++-- convex/entries.ts | 99 ++++++++++++++++ convex/schema.ts | 6 + convex/validators.ts | 7 +- src/lib/components/EntryViewModal.svelte | 104 +++++++++++++++++ src/lib/components/MarkdownToolbar.svelte | 59 ++++++++++ src/routes/+layout.svelte | 124 ++++++++++++++------ src/routes/dashboard/+page.svelte | 47 ++++++-- src/routes/entries/+page.svelte | 133 ++++++++++++++++++++-- src/routes/settings/+page.svelte | 3 +- 10 files changed, 549 insertions(+), 62 deletions(-) create mode 100644 src/lib/components/EntryViewModal.svelte create mode 100644 src/lib/components/MarkdownToolbar.svelte diff --git a/convex/analytics.ts b/convex/analytics.ts index e1b664a..26e7838 100644 --- a/convex/analytics.ts +++ b/convex/analytics.ts @@ -1,5 +1,13 @@ import { query } from './_generated/server.js'; import { requireOwner } from './lib.js'; +import { dashboardRangeValidator } from './validators.js'; + +const rangeDays = { + '7d': 7, + '30d': 30, + '90d': 90, + '1y': 365 +} as const; function dayKey(date: Date) { return date.toISOString().slice(0, 10); @@ -13,24 +21,27 @@ function daysAgo(days: number) { } export const summary = query({ - args: {}, - handler: async (ctx) => { + args: { range: dashboardRangeValidator }, + handler: async (ctx, args) => { const owner = await requireOwner(ctx); + const selectedDays = rangeDays[args.range]; const entries = await ctx.db .query('entries') .withIndex('by_owner_and_entryDate', (q) => q.eq('owner', owner)) .order('desc') - .take(250); + .take(Math.min(800, selectedDays * 3)); const activeEntries = entries.filter((entry) => !entry.archived); - const last30Start = daysAgo(29); + const rangeStart = daysAgo(selectedDays - 1); const last7Start = daysAgo(6); - const last30 = activeEntries.filter((entry) => entry.entryDate >= last30Start); + const last30Start = daysAgo(29); + const rangedEntries = activeEntries.filter((entry) => entry.entryDate >= rangeStart); const last7 = activeEntries.filter((entry) => entry.entryDate >= last7Start); + const last30 = activeEntries.filter((entry) => entry.entryDate >= last30Start); const moodCounts: Record = {}; const dailyCounts = new Map(); const tagCounts: Record = {}; - for (const entry of last30) { + for (const entry of rangedEntries) { moodCounts[entry.mood] = (moodCounts[entry.mood] ?? 0) + 1; dailyCounts.set(entry.entryDate, (dailyCounts.get(entry.entryDate) ?? 0) + 1); @@ -46,8 +57,8 @@ export const summary = query({ currentStreak += 1; } - const dailySeries = Array.from({ length: 30 }, (_, index) => { - const date = daysAgo(29 - index); + const dailySeries = Array.from({ length: selectedDays }, (_, index) => { + const date = daysAgo(selectedDays - 1 - index); return { date, count: dailyCounts.get(date) ?? 0 }; }); @@ -55,6 +66,8 @@ export const summary = query({ totalEntries: activeEntries.length, entriesThisWeek: last7.length, entriesThisMonth: last30.length, + entriesInRange: rangedEntries.length, + range: args.range, currentStreak, averageWords: activeEntries.length === 0 diff --git a/convex/entries.ts b/convex/entries.ts index 6f849dc..9e88a7b 100644 --- a/convex/entries.ts +++ b/convex/entries.ts @@ -19,6 +19,46 @@ const sortValidator = v.union( v.literal('title') ); +const markdownGuideBody = `# Markdown basics + +Welcome to Journaley. This starter entry shows the markdown features you can use while journaling. + +## Formatting + +Use **bold** for emphasis, _italic_ for gentle notes, and \`inline code\` for short snippets. + +## Lists + +- Capture loose thoughts +- Group ideas by theme +- Keep reflections scannable + +1. Start with what happened +2. Add how it felt +3. Close with one next step + +## Tasks + +- [ ] Try writing one short entry +- [ ] Add a few tags +- [ ] Search for this guide later + +## Quotes + +> A small daily note is still a thread you can return to. + +## Links and tables + +[Markdown guide](https://www.markdownguide.org/basic-syntax/) + +| Syntax | Result | +| --- | --- | +| **bold** | bold text | +| _italic_ | italic text | +| # Heading | section title | + +You can edit or delete this entry whenever you want.`; + type EntryWithTags = Doc<'entries'> & { tags: Doc<'tags'>[]; }; @@ -258,6 +298,65 @@ export const create = mutation({ } }); +export const ensureMarkdownGuideEntry = mutation({ + args: {}, + handler: async (ctx) => { + const owner = await requireOwner(ctx); + const existingState = await ctx.db + .query('userState') + .withIndex('by_owner', (q) => q.eq('owner', owner)) + .unique(); + + if (existingState?.markdownGuideSeeded) { + return existingState.markdownGuideEntryId ?? null; + } + + const tags = await ensureTags(ctx, owner, ['markdown', 'guide']); + const now = Date.now(); + const today = new Date().toISOString().slice(0, 10); + const plainText = markdownToPlainText(markdownGuideBody); + const entryId = await ctx.db.insert('entries', { + owner, + title: 'Markdown basics', + body: markdownGuideBody, + plainText, + searchText: buildSearchText({ + title: 'Markdown basics', + body: markdownGuideBody, + mood: 'neutral', + tagNames: tags.map((tag) => tag.name) + }), + mood: 'neutral', + entryDate: today, + tagNames: tags.map((tag) => tag.name), + tagSlugs: tags.map((tag) => tag.slug), + pinned: true, + archived: false, + createdAt: now, + updatedAt: now + }); + + await applyEntryTags(ctx, owner, entryId, tags); + + if (existingState) { + await ctx.db.patch(existingState._id, { + markdownGuideSeeded: true, + markdownGuideEntryId: entryId, + updatedAt: now + }); + } else { + await ctx.db.insert('userState', { + owner, + markdownGuideSeeded: true, + markdownGuideEntryId: entryId, + updatedAt: now + }); + } + + return entryId; + } +}); + export const update = mutation({ args: { entryId: v.id('entries'), diff --git a/convex/schema.ts b/convex/schema.ts index 180b24c..c981edc 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -61,5 +61,11 @@ export default defineSchema({ editorMode: editorModeValidator, dashboardRange: dashboardRangeValidator, updatedAt: v.number() + }).index('by_owner', ['owner']), + userState: defineTable({ + owner: v.string(), + markdownGuideSeeded: v.boolean(), + markdownGuideEntryId: v.optional(v.id('entries')), + updatedAt: v.number() }).index('by_owner', ['owner']) }); diff --git a/convex/validators.ts b/convex/validators.ts index 79b3941..917782d 100644 --- a/convex/validators.ts +++ b/convex/validators.ts @@ -23,4 +23,9 @@ export const editorModeValidator = v.union( v.literal('preview') ); -export const dashboardRangeValidator = v.union(v.literal('7d'), v.literal('30d'), v.literal('90d')); +export const dashboardRangeValidator = v.union( + v.literal('7d'), + v.literal('30d'), + v.literal('90d'), + v.literal('1y') +); diff --git a/src/lib/components/EntryViewModal.svelte b/src/lib/components/EntryViewModal.svelte new file mode 100644 index 0000000..705627e --- /dev/null +++ b/src/lib/components/EntryViewModal.svelte @@ -0,0 +1,104 @@ + + +
+
+
+
+
+
+ + {entry.mood} + + {#if entry.pinned} + + + Pinned + + {/if} + {#if entry.archived} + + + Archived + + {/if} +
+

{entry.title}

+

+ {formatEntryDate(entry.entryDate)} ยท Updated {formatEntryDate(entry.updatedAt, { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: '2-digit' + })} +

+
+ + +
+ + {#if entry.tags.length} +
+ {#each entry.tags as tag (tag._id)} + + #{tag.name} + + {/each} +
+ {/if} +
+ +
+ +
+ + {#if onEdit || onDelete} +
+ {#if onEdit} + + {/if} + {#if onDelete} + + {/if} +
+ {/if} +
+
diff --git a/src/lib/components/MarkdownToolbar.svelte b/src/lib/components/MarkdownToolbar.svelte new file mode 100644 index 0000000..ced8e4e --- /dev/null +++ b/src/lib/components/MarkdownToolbar.svelte @@ -0,0 +1,59 @@ + + +
+ {#each actions as item (item.action)} + {@const Icon = item.icon} + + {/each} +
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3967f00..9ce2b4e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,8 +1,11 @@ {#if isLoading && !isAuthRoute} @@ -36,52 +60,92 @@ {:else if isAuthenticated && !isAuthRoute} -
-