From 69b57f8aa1d4d003f943cf9b1cbe4ae531c67d81 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 17 Jun 2026 11:34:39 +0300 Subject: [PATCH] Add URL content type support and enhance snippet functionality: - Introduced 'url' as a new content type in the application. - Updated database queries to include copy count for snippets. - Enhanced UI components to display copy count and handle URL snippets. --- pnpm-workspace.yaml | 2 + src-tauri/src/commands/snippets.rs | 18 ++-- src-tauri/src/models.rs | 4 + src-tauri/tauri.conf.json | 4 +- src/app.css | 3 + src/lib/components/SnippetCard.svelte | 19 +++- src/lib/components/SnippetDetail.svelte | 53 +++++++++- src/lib/components/layout/CenterPanel.svelte | 22 +++- src/lib/components/layout/LeftPanel.svelte | 102 ++++++++++++++----- src/lib/highlight.ts | 17 ++++ src/lib/stores/snippets.svelte.ts | 44 +++++++- src/lib/types.ts | 3 +- src/routes/+page.svelte | 14 ++- src/routes/quicknote/+page.svelte | 2 +- 14 files changed, 256 insertions(+), 51 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4de91a3..0872e9a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,4 @@ packages: - '.' +allowBuilds: + esbuild: true diff --git a/src-tauri/src/commands/snippets.rs b/src-tauri/src/commands/snippets.rs index 8e6953c..10a3b1a 100644 --- a/src-tauri/src/commands/snippets.rs +++ b/src-tauri/src/commands/snippets.rs @@ -25,7 +25,7 @@ fn load_tags(conn: &Connection, snippet_id: i64) -> Result, AppError> { fn load_snippet(conn: &Connection, id: i64) -> Result { let mut snippet = conn.query_row( - "SELECT id, title, content, content_type, created_at, updated_at \ + "SELECT id, title, content, content_type, copy_count, created_at, updated_at \ FROM snippet WHERE id = ?1", [id], |r| { @@ -35,8 +35,9 @@ fn load_snippet(conn: &Connection, id: i64) -> Result { title: r.get(1)?, content: r.get(2)?, content_type: ct.parse::().unwrap_or(ContentType::Text), - created_at: r.get(4)?, - updated_at: r.get(5)?, + copy_count: r.get(4)?, + created_at: r.get(5)?, + updated_at: r.get(6)?, tags: vec![], }) }, @@ -71,7 +72,7 @@ fn load_snippets_for_ids(conn: &Connection, ids: &[i64]) -> Result, .collect::>() .join(", "); let snippet_sql = format!( - "SELECT id, title, content, content_type, created_at, updated_at \ + "SELECT id, title, content, content_type, copy_count, created_at, updated_at \ FROM snippet WHERE id IN ({placeholders})" ); let mut stmt = conn.prepare(&snippet_sql)?; @@ -83,8 +84,9 @@ fn load_snippets_for_ids(conn: &Connection, ids: &[i64]) -> Result, title: r.get(1)?, content: r.get(2)?, content_type: ct.parse::().unwrap_or(ContentType::Text), - created_at: r.get(4)?, - updated_at: r.get(5)?, + copy_count: r.get(4)?, + created_at: r.get(5)?, + updated_at: r.get(6)?, tags: vec![], }) })? @@ -119,7 +121,7 @@ fn db_list_snippets( let ids: Vec = match tag_filter { None => { let mut stmt = conn - .prepare("SELECT id FROM snippet ORDER BY created_at DESC, id DESC")?; + .prepare("SELECT id FROM snippet ORDER BY copy_count DESC, created_at DESC, id DESC")?; let rows = stmt.query_map([], |r| r.get(0))? .collect::, _>>()?; rows @@ -130,7 +132,7 @@ fn db_list_snippets( JOIN snippet_tag st ON st.snippet_id = s.id \ JOIN tag t ON t.id = st.tag_id \ WHERE t.name = ?1 AND st.source != 'suppressed' \ - ORDER BY s.created_at DESC, s.id DESC", + ORDER BY s.copy_count DESC, s.created_at DESC, s.id DESC", )?; let rows = stmt.query_map([tag], |r| r.get(0))? .collect::, _>>()?; diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 172d21a..835a8bb 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -6,6 +6,7 @@ pub enum ContentType { Code, Cli, Text, + Url, } impl std::fmt::Display for ContentType { @@ -14,6 +15,7 @@ impl std::fmt::Display for ContentType { ContentType::Code => write!(f, "code"), ContentType::Cli => write!(f, "cli"), ContentType::Text => write!(f, "text"), + ContentType::Url => write!(f, "url"), } } } @@ -25,6 +27,7 @@ impl std::str::FromStr for ContentType { "code" => Ok(ContentType::Code), "cli" => Ok(ContentType::Cli), "text" => Ok(ContentType::Text), + "url" => Ok(ContentType::Url), other => Err(format!("unknown content_type: {other}")), } } @@ -36,6 +39,7 @@ pub struct Snippet { pub title: String, pub content: String, pub content_type: ContentType, + pub copy_count: i64, pub created_at: String, pub updated_at: String, pub tags: Vec, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c774254..b72a450 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -13,8 +13,8 @@ "windows": [ { "title": "Faro", - "width": 1100, - "height": 720, + "width": 1300, + "height": 900, "minWidth": 800, "minHeight": 520, "resizable": true diff --git a/src/app.css b/src/app.css index 73b0493..3bef380 100644 --- a/src/app.css +++ b/src/app.css @@ -43,6 +43,7 @@ /* semantic */ --success: #c3e88d; --danger: #ef4444; + --link: #60a5fa; /* syntax */ --syntax-keyword: #c792ea; @@ -95,6 +96,7 @@ --accent-border: #6d5dfc; --success: #16a34a; --danger: #dc2626; + --link: #2563eb; --syntax-keyword: #2563eb; --syntax-type: #0891b2; --syntax-func: #7c3aed; @@ -133,6 +135,7 @@ --accent-border: #88c0d0; --success: #a3be8c; --danger: #bf616a; + --link: #5e81ac; --syntax-keyword: #81a1c1; --syntax-type: #8fbcbb; --syntax-func: #88c0d0; diff --git a/src/lib/components/SnippetCard.svelte b/src/lib/components/SnippetCard.svelte index d980122..052ef42 100644 --- a/src/lib/components/SnippetCard.svelte +++ b/src/lib/components/SnippetCard.svelte @@ -1,7 +1,8 @@ @@ -52,11 +53,16 @@ {:else if snippet.content_type === 'cli'} + {:else if snippet.content_type === 'url'} + {:else} {/if} {snippet.title || 'Untitled'} + {#if snippet.copy_count > 0} + {snippet.copy_count} + {/if} + {/if} + import SnippetCard from '$lib/components/SnippetCard.svelte'; import SearchBar from '$lib/components/SearchBar.svelte'; - import type { Snippet, TimePeriod } from '$lib/types'; + import type { Snippet, TimePeriod, ContentType } from '$lib/types'; - let { snippets, selectedId, loading = false, searchQuery, tagFilter = null, timePeriodFilter = null, onSelect, onSearch, searchInputEl = $bindable() }: { + let { snippets, selectedId, loading = false, searchQuery, tagFilter = null, contentTypeFilter = null, timePeriodFilter = null, onSelect, onSearch, searchInputEl = $bindable() }: { snippets: Snippet[]; selectedId: number | null; loading?: boolean; searchQuery: string; tagFilter?: string | null; + contentTypeFilter?: ContentType | null; timePeriodFilter?: TimePeriod | null; onSelect: (id: number) => void; onSearch: (q: string) => void; @@ -23,14 +24,25 @@ 'older': 'older than two weeks', }; + const CT_LABELS: Record = { + code: 'code', + cli: 'CLI', + text: 'text', + url: 'URL', + }; + const emptyMessage = $derived( searchQuery ? 'No snippets match your search.' : timePeriodFilter ? `No snippets from ${PERIOD_LABELS[timePeriodFilter]}.` - : tagFilter - ? `No snippets tagged "${tagFilter}".` - : 'No snippets yet — create one with the button on the left.' + : tagFilter && contentTypeFilter + ? `No ${CT_LABELS[contentTypeFilter]} snippets tagged "${tagFilter}".` + : tagFilter + ? `No snippets tagged "${tagFilter}".` + : contentTypeFilter + ? `No ${CT_LABELS[contentTypeFilter]} snippets.` + : 'No snippets yet — create one with the button on the left.' ); diff --git a/src/lib/components/layout/LeftPanel.svelte b/src/lib/components/layout/LeftPanel.svelte index c578579..f845fe8 100644 --- a/src/lib/components/layout/LeftPanel.svelte +++ b/src/lib/components/layout/LeftPanel.svelte @@ -1,6 +1,6 @@