use pulldown_cmark::{html as md_html, Options, Parser}; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; use web_sys::{HtmlInputElement, MouseEvent}; use yew::prelude::*; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], catch)] async fn invoke(cmd: &str, args: JsValue) -> Result; } #[derive(PartialEq, Clone, Copy)] pub enum NewsMode { Ai, Rss, } #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] #[serde(rename_all = "camelCase")] pub struct NewsArticle { pub content: String, pub author: String, pub created_at: String, } #[derive(Serialize)] struct StringArgs { key: String, } #[derive(Serialize)] struct UrlsArgs { urls: Vec, } #[function_component(News)] pub fn news() -> Html { let articles = use_state(|| None::>); let error_msg = use_state(|| None::); let show_config = use_state(|| false); let current_mode = use_state(|| NewsMode::Ai); let rss_urls = use_state(Vec::::new); let api_key_ref = use_node_ref(); let new_url_ref = use_node_ref(); // Init: Konfiguration laden { let rss_urls = rss_urls.clone(); use_effect_with((), move |_| { spawn_local(async move { if let Ok(res) = invoke("load_rss_config", JsValue::NULL).await { if let Ok(urls) = serde_wasm_bindgen::from_value::>(res) { rss_urls.set(urls); } } }); || () }); } // News laden Funktion let load_news = { let articles = articles.clone(); let error_msg = error_msg.clone(); let mode = *current_mode; Callback::from(move |_| { let articles = articles.clone(); let error_msg = error_msg.clone(); articles.set(None); error_msg.set(None); spawn_local(async move { let cmd = if mode == NewsMode::Ai { "fetch_ai_news" } else { "fetch_rss_news" }; match invoke(cmd, JsValue::NULL).await { Ok(res) => { let data = serde_wasm_bindgen::from_value::>(res) .unwrap_or_default(); articles.set(Some(data)); } Err(e) => error_msg.set(Some(e.as_string().unwrap_or_default())), } }); }) }; // Automatisches Laden bei Modus-Wechsel { let load_news = load_news.clone(); use_effect_with((*current_mode).clone(), move |_| { load_news.emit(MouseEvent::new("click").unwrap()); || () }); } let save_settings = { let rss_urls = rss_urls.clone(); let api_key_ref = api_key_ref.clone(); let show_config = show_config.clone(); let load_news = load_news.clone(); Callback::from(move |_| { let key = api_key_ref .cast::() .map(|i| i.value()) .unwrap_or_default(); let urls = (*rss_urls).clone(); let show_config = show_config.clone(); let load_news = load_news.clone(); spawn_local(async move { let _ = invoke( "save_openrouter_key", serde_wasm_bindgen::to_value(&StringArgs { key }).unwrap(), ) .await; let _ = invoke( "save_rss_urls", serde_wasm_bindgen::to_value(&UrlsArgs { urls }).unwrap(), ) .await; show_config.set(false); load_news.emit(MouseEvent::new("click").unwrap()); }); }) }; html! { { if *current_mode == NewsMode::Ai { "📰 KI News" } else { "📻 RSS Feeds" } } {"KI"} {"RSS"} {"⚙️"} {"🔄"} if *show_config { {"OPENROUTER API KEY"} {"RSS FEEDS"} () { let val = input.value(); if !val.trim().is_empty() { let mut list = (*rss_urls).clone(); list.push(val); rss_urls.set(list); input.set_value(""); } } } }>{"+"} { for (*rss_urls).iter().enumerate().map(|(i, url)| html! { {url} {"✕"} }) } {"Alles Speichern"} } { if let Some(msg) = (*error_msg).clone() { html! { { format!("⚠️ Error: {}", msg) } } } else if let Some(list) = (*articles).clone() { if list.is_empty() { html! { {"Keine Nachrichten gefunden. Überprüfe deine Feeds."} } } else { html! { for list.iter().map(|a| html! { }) } } } else { html! { {"Lade Nachrichten..."} } } } } } #[derive(Properties, PartialEq)] pub struct NewsCardProps { pub article: NewsArticle, } #[function_component(NewsCard)] pub fn news_card(props: &NewsCardProps) -> Html { let a = &props.article; let markdown_html = { let mut options = Options::empty(); options.insert(Options::ENABLE_STRIKETHROUGH); let parser = Parser::new_ext(&a.content, options); let mut out = String::new(); md_html::push_html(&mut out, parser); out }; html! { { &a.author } { &a.created_at } { Html::from_html_unchecked(AttrValue::from(markdown_html)) } } }
{"Keine Nachrichten gefunden. Überprüfe deine Feeds."}