diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d0231114..8fc2c89a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1082,6 +1082,36 @@ dependencies = [ "cipher", ] +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2 0.4.9", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.65+curl-8.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -2098,7 +2128,21 @@ checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" dependencies = [ "log", "mac", - "markup5ever", + "markup5ever 0.10.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever 0.11.0", "proc-macro2", "quote", "syn 1.0.109", @@ -2462,7 +2506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" dependencies = [ "cssparser", - "html5ever", + "html5ever 0.25.2", "matches", "selectors", ] @@ -2511,6 +2555,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "line-wrap" version = "0.1.1" @@ -2579,6 +2635,7 @@ dependencies = [ "tauri-plugin-store", "tauri-plugin-stronghold", "tauri-plugin-upload", + "webpage", "window-shadows", "window-vibrancy", ] @@ -2619,12 +2676,38 @@ checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" dependencies = [ "log", "phf 0.8.0", - "phf_codegen", + "phf_codegen 0.8.0", "string_cache", "string_cache_codegen", "tendril", ] +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2" +dependencies = [ + "html5ever 0.26.0", + "markup5ever 0.11.0", + "tendril", + "xml5ever", +] + [[package]] name = "matchers" version = "0.1.0" @@ -3205,6 +3288,16 @@ dependencies = [ "phf_shared 0.8.0", ] +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + [[package]] name = "phf_generator" version = "0.8.0" @@ -4022,7 +4115,7 @@ dependencies = [ "log", "matches", "phf 0.8.0", - "phf_codegen", + "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", @@ -5096,7 +5189,7 @@ dependencies = [ "dunce", "glob", "heck 0.4.1", - "html5ever", + "html5ever 0.25.2", "infer", "json-patch", "kuchiki", @@ -5754,6 +5847,19 @@ dependencies = [ "system-deps 6.1.1", ] +[[package]] +name = "webpage" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8598785beeb5af95abe95e7bb20c7e747d1188347080d6811d5a56d2b9a5f368" +dependencies = [ + "curl", + "html5ever 0.26.0", + "markup5ever_rcdom", + "serde", + "serde_json", +] + [[package]] name = "webpki-roots" version = "0.24.0" @@ -6232,7 +6338,7 @@ dependencies = [ "gio", "glib", "gtk", - "html5ever", + "html5ever 0.25.2", "http", "kuchiki", "libc", @@ -6305,6 +6411,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "xml5ever" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650" +dependencies = [ + "log", + "mac", + "markup5ever 0.11.0", +] + [[package]] name = "zbus" version = "3.14.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 844453a6..b08fcb19 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -50,6 +50,7 @@ sqlx-cli = { version = "0.7.0", default-features = false, features = [ "sqlite", ] } rust-argon2 = "1.0" +webpage = { version = "1.6.0", features = ["serde"] } [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 18e6bc3a..6b3af0b6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,6 +7,8 @@ use tauri::Manager; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_sql::{Migration, MigrationKind}; use window_shadows::set_shadow; +use webpage::{Webpage, WebpageOptions}; +use std::time::Duration; #[cfg(target_os = "macos")] use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial}; @@ -17,6 +19,71 @@ struct Payload { cwd: String, } +#[derive(serde::Serialize)] +struct OpenGraphResponse { + title: String, + description: String, + url: String, + image: String, +} + +async fn fetch_opengraph(url: String) -> OpenGraphResponse { + let options = WebpageOptions { + allow_insecure: false, + max_redirections: 3, + timeout: Duration::from_secs(15), + useragent: "lume - desktop app".to_string(), + ..Default::default() + }; + + let result = match Webpage::from_url(&url, options) { + Ok(webpage) => webpage, + Err(_) => { + return OpenGraphResponse { + title: "".to_string(), + description: "".to_string(), + url: "".to_string(), + image: "".to_string(), + } + } + }; + + let html = result.html; + + return OpenGraphResponse { + title: html + .opengraph + .properties + .get("title") + .cloned() + .unwrap_or_default(), + description: html + .opengraph + .properties + .get("description") + .cloned() + .unwrap_or_default(), + url: html + .opengraph + .properties + .get("url") + .cloned() + .unwrap_or_default(), + image: html + .opengraph + .images + .get(0) + .and_then(|i| Some(i.url.clone())) + .unwrap_or_default(), + }; +} + +#[tauri::command] +async fn opengraph(url: String) -> OpenGraphResponse { + let result = fetch_opengraph(url).await; + return result; +} + #[tauri::command] async fn close_splashscreen(window: tauri::Window) { // Close splashscreen @@ -186,7 +253,7 @@ fn main() { })) .plugin(tauri_plugin_upload::init()) .plugin(tauri_plugin_store::Builder::default().build()) - .invoke_handler(tauri::generate_handler![close_splashscreen]) + .invoke_handler(tauri::generate_handler![close_splashscreen, opengraph]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src/shared/notes/child.tsx b/src/shared/notes/child.tsx index 9b0d67bd..88a7b55c 100644 --- a/src/shared/notes/child.tsx +++ b/src/shared/notes/child.tsx @@ -15,27 +15,6 @@ import { useEvent } from '@utils/hooks/useEvent'; export function ChildNote({ id, root }: { id: string; root?: string }) { const { status, data } = useEvent(id); - if (status === 'loading') { - return ( -
- -
- ); - } - - if (status === 'error') { - return ( -
-

- Lume cannot found the event with ID -

-
-

{id}

-
-
- ); - } - const renderKind = (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: @@ -49,6 +28,40 @@ export function ChildNote({ id, root }: { id: string; root?: string }) { } }; + if (status === 'loading') { + return ( + <> +
+
+ +
+ + ); + } + + if (status === 'error') { + return ( + <> +
+
+
+
+
+ Lume (System) +
+
+
+
+
+

Event not found, click to open this note via nostr.com

+

{id}

+
+
+
+ + ); + } + return ( <>
diff --git a/src/shared/notes/mentions/note.tsx b/src/shared/notes/mentions/note.tsx index 79895e68..4be2bf74 100644 --- a/src/shared/notes/mentions/note.tsx +++ b/src/shared/notes/mentions/note.tsx @@ -46,7 +46,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) { if (status === 'loading') { return ( -
+
); @@ -54,8 +54,11 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) { if (status === 'error') { return ( -
-

Can't get event from relay, ID: {id}

+
+
Event not found
+
+ {id} +
); } diff --git a/src/shared/notes/preview/link.tsx b/src/shared/notes/preview/link.tsx index 9d7bbdb6..38beac9f 100644 --- a/src/shared/notes/preview/link.tsx +++ b/src/shared/notes/preview/link.tsx @@ -1,3 +1,5 @@ +import { Link } from 'react-router-dom'; + import { Image } from '@shared/image'; import { useOpenGraph } from '@utils/hooks/useOpenGraph'; @@ -20,11 +22,11 @@ export function LinkPreview({ urls }: { urls: string[] }) {
) : ( - {error ? (
@@ -42,22 +44,22 @@ export function LinkPreview({ urls }: { urls: string[] }) { className="h-44 w-full rounded-t-lg object-cover" /> )} - ); diff --git a/src/utils/hooks/useOpenGraph.ts b/src/utils/hooks/useOpenGraph.ts index 2dda8901..9759ec80 100644 --- a/src/utils/hooks/useOpenGraph.ts +++ b/src/utils/hooks/useOpenGraph.ts @@ -5,7 +5,7 @@ import { Opengraph } from '@utils/types'; export function useOpenGraph(url: string) { const { status, data, error } = useQuery( - ['preview', url], + ['opg', url], async () => { const res: Opengraph = await invoke('opengraph', { url }); if (!res) { @@ -14,10 +14,10 @@ export function useOpenGraph(url: string) { return res; }, { + staleTime: Infinity, refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false, - staleTime: Infinity, } ); diff --git a/src/utils/parser.ts b/src/utils/parser.ts index 762eb68d..aa7b39bb 100644 --- a/src/utils/parser.ts +++ b/src/utils/parser.ts @@ -45,6 +45,8 @@ export function parser(eventContent: string) { // remove url from original content word = word.replace(word, ''); } + + content.links.push(url.toString()); } // hashtag