diff --git a/crates/pinakes-ui/Cargo.toml b/crates/pinakes-ui/Cargo.toml index 24eb8f5..bfa7b34 100644 --- a/crates/pinakes-ui/Cargo.toml +++ b/crates/pinakes-ui/Cargo.toml @@ -17,8 +17,9 @@ reqwest = { workspace = true } dioxus = { workspace = true } tokio = { workspace = true } futures = { workspace = true } -rfd = "0.17" +rfd = { workspace = true} pulldown-cmark = { workspace = true } gray_matter = { workspace = true } regex = { workspace = true } -ammonia = "4" +ammonia = { workspace = true} +dioxus-free-icons = {workspace = true} diff --git a/crates/pinakes-ui/src/client.rs b/crates/pinakes-ui/src/client.rs index cd1f740..a52860b 100644 --- a/crates/pinakes-ui/src/client.rs +++ b/crates/pinakes-ui/src/client.rs @@ -1168,7 +1168,11 @@ impl ApiClient { } /// Get graph data for visualization. - pub async fn get_graph(&self, center_id: Option<&str>, depth: Option) -> Result { + pub async fn get_graph( + &self, + center_id: Option<&str>, + depth: Option, + ) -> Result { let mut url = self.url("/notes/graph"); let mut query_parts = Vec::new(); if let Some(center) = center_id { diff --git a/crates/pinakes-ui/src/components/backlinks_panel.rs b/crates/pinakes-ui/src/components/backlinks_panel.rs index 7336135..9f4df1f 100644 --- a/crates/pinakes-ui/src/components/backlinks_panel.rs +++ b/crates/pinakes-ui/src/components/backlinks_panel.rs @@ -94,7 +94,11 @@ pub fn BacklinksPanel( collapsed.set(!current); }, span { class: "backlinks-toggle", - if is_collapsed { "\u{25b6}" } else { "\u{25bc}" } + if is_collapsed { + "\u{25b6}" + } else { + "\u{25bc}" + } } span { class: "backlinks-title", "Backlinks" } span { class: "backlinks-count", "({count})" } @@ -116,8 +120,7 @@ pub fn BacklinksPanel( div { class: "backlinks-content", // Show reindex message if present if let Some((ref msg, is_err)) = *reindex_message.read() { - div { - class: if is_err { "backlinks-message error" } else { "backlinks-message success" }, + div { class: if is_err { "backlinks-message error" } else { "backlinks-message success" }, "{msg}" } } @@ -136,9 +139,7 @@ pub fn BacklinksPanel( if !is_loading && error.read().is_none() { if let Some(ref data) = *backlink_data { if data.backlinks.is_empty() { - div { class: "backlinks-empty", - "No other notes link to this one." - } + div { class: "backlinks-empty", "No other notes link to this one." } } else { ul { class: "backlinks-list", for backlink in &data.backlinks { @@ -159,10 +160,7 @@ pub fn BacklinksPanel( /// Individual backlink item view. #[component] -fn BacklinkItemView( - backlink: BacklinkItem, - on_navigate: EventHandler, -) -> Element { +fn BacklinkItemView(backlink: BacklinkItem, on_navigate: EventHandler) -> Element { let source_id = backlink.source_id.clone(); let title = backlink .source_title @@ -250,7 +248,11 @@ pub fn OutgoingLinksPanel( collapsed.set(!current); }, span { class: "outgoing-links-toggle", - if is_collapsed { "\u{25b6}" } else { "\u{25bc}" } + if is_collapsed { + "\u{25b6}" + } else { + "\u{25bc}" + } } span { class: "outgoing-links-title", "Outgoing Links" } span { class: "outgoing-links-count", "({count})" } @@ -279,9 +281,7 @@ pub fn OutgoingLinksPanel( if !is_loading && error.read().is_none() { if let Some(ref data) = *link_data { if data.links.is_empty() { - div { class: "outgoing-links-empty", - "This note has no outgoing links." - } + div { class: "outgoing-links-empty", "This note has no outgoing links." } } else { ul { class: "outgoing-links-list", for link in &data.links { @@ -323,7 +323,11 @@ fn OutgoingLinkItemView( let link_type = link.link_type.clone(); let display_text = link_text.unwrap_or_else(|| target_path.clone()); - let resolved_class = if is_resolved { "resolved" } else { "unresolved" }; + let resolved_class = if is_resolved { + "resolved" + } else { + "unresolved" + }; rsx! { li { diff --git a/crates/pinakes-ui/src/components/graph_view.rs b/crates/pinakes-ui/src/components/graph_view.rs index 1e24885..6d83c44 100644 --- a/crates/pinakes-ui/src/components/graph_view.rs +++ b/crates/pinakes-ui/src/components/graph_view.rs @@ -68,9 +68,7 @@ pub fn GraphView( } } if let Some(ref data) = *data { - div { class: "graph-stats", - "{data.node_count} nodes, {data.edge_count} edges" - } + div { class: "graph-stats", "{data.node_count} nodes, {data.edge_count} edges" } } } @@ -176,8 +174,9 @@ fn GraphSvg( for edge in &edges { if let (Some(&(x1, y1)), Some(&(x2, y2))) = ( id_to_pos.get(edge.source.as_str()), - id_to_pos.get(edge.target.as_str()) - ) { + id_to_pos.get(edge.target.as_str()), + ) + { line { class: "graph-edge edge-type-{edge.link_type}", x1: "{x1}", @@ -201,16 +200,13 @@ fn GraphSvg( ref_x: "10", ref_y: "3.5", orient: "auto", - polygon { - points: "0 0, 10 3.5, 0 7", - fill: "#888", - } + polygon { points: "0 0, 10 3.5, 0 7", fill: "#888" } } } // Draw nodes g { class: "graph-nodes", - for (i, node) in nodes.iter().enumerate() { + for (i , node) in nodes.iter().enumerate() { { let (x, y) = positions[i]; let node_id = node.id.clone(); @@ -226,6 +222,7 @@ fn GraphSvg( onclick: move |_| on_node_click.call(node_id.clone()), ondoubleclick: move |_| on_node_double_click.call(node_id2.clone()), + circle { cx: "{x}", cy: "{y}", @@ -264,11 +261,7 @@ fn NodeDetailsPanel( div { class: "node-details-panel", div { class: "node-details-header", h3 { "{node.label}" } - button { - class: "close-btn", - onclick: move |_| on_close.call(()), - "\u{2715}" - } + button { class: "close-btn", onclick: move |_| on_close.call(()), "\u{2715}" } } div { class: "node-details-content", if let Some(ref title) = node.title { diff --git a/crates/pinakes-ui/src/components/markdown_viewer.rs b/crates/pinakes-ui/src/components/markdown_viewer.rs index 3ad3b34..1a31980 100644 --- a/crates/pinakes-ui/src/components/markdown_viewer.rs +++ b/crates/pinakes-ui/src/components/markdown_viewer.rs @@ -227,11 +227,55 @@ fn sanitize_html(html: &str) -> String { // Allow common markdown elements let allowed_tags: HashSet<&str> = [ - "a", "abbr", "acronym", "b", "blockquote", "br", "code", "dd", "del", - "details", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", - "hr", "i", "img", "ins", "kbd", "li", "mark", "ol", "p", "pre", "q", - "s", "samp", "small", "span", "strong", "sub", "summary", "sup", - "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul", "var", + "a", + "abbr", + "acronym", + "b", + "blockquote", + "br", + "code", + "dd", + "del", + "details", + "div", + "dl", + "dt", + "em", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + "i", + "img", + "ins", + "kbd", + "li", + "mark", + "ol", + "p", + "pre", + "q", + "s", + "samp", + "small", + "span", + "strong", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "tfoot", + "th", + "thead", + "tr", + "u", + "ul", + "var", // Task list support "input", ] @@ -240,13 +284,27 @@ fn sanitize_html(html: &str) -> String { // Allow safe attributes let allowed_attrs: HashSet<&str> = [ - "href", "src", "alt", "title", "class", "id", "name", - "width", "height", "align", "valign", - "colspan", "rowspan", "scope", + "href", + "src", + "alt", + "title", + "class", + "id", + "name", + "width", + "height", + "align", + "valign", + "colspan", + "rowspan", + "scope", // Data attributes for wikilinks (safe - no code execution) - "data-target", "data-wikilink-target", + "data-target", + "data-wikilink-target", // Task list checkbox support - "type", "checked", "disabled", + "type", + "checked", + "disabled", ] .into_iter() .collect();