re-add link preview

This commit is contained in:
Ren Amamiya 2023-09-04 14:35:57 +07:00
parent 3ebcf4a981
commit c74a81cfdb
8 changed files with 246 additions and 41 deletions

129
src-tauri/Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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");
}

View File

@ -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 (
<div className="relative mb-5 overflow-hidden rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
<NoteSkeleton />
</div>
);
}
if (status === 'error') {
return (
<div className="mb-5 flex flex-col gap-1.5 overflow-hidden rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
<p className="text-sm font-bold text-white">
Lume cannot found the event with ID
</p>
<div className="inline-flex items-center justify-center rounded-xl border border-dashed border-red-400 bg-red-200/10 p-2">
<p className="select-text break-all text-sm font-medium text-red-400">{id}</p>
</div>
</div>
);
}
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 (
<>
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.4rem)] w-0.5 bg-gradient-to-t from-white/20 to-white/10" />
<div className="relative mb-5 overflow-hidden">
<NoteSkeleton />
</div>
</>
);
}
if (status === 'error') {
return (
<>
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.4rem)] w-0.5 bg-gradient-to-t from-white/20 to-white/10" />
<div className="relative mb-5 flex flex-col">
<div className="relative z-10 flex items-start gap-3">
<div className="h-11 w-11 rounded-lg bg-black" />
<h5 className="truncate font-semibold leading-none text-white">
Lume (System)
</h5>
</div>
<div className="-mt-6 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="markdown relative z-20 flex-1 select-text">
<p>Event not found, click to open this note via nostr.com</p>
<p>{id}</p>
</div>
</div>
</div>
</>
);
}
return (
<>
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.4rem)] w-0.5 bg-gradient-to-t from-white/20 to-white/10" />

View File

@ -46,7 +46,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
if (status === 'loading') {
return (
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl">
<div className="mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl">
<NoteSkeleton />
</div>
);
@ -54,8 +54,11 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
if (status === 'error') {
return (
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl">
<p>Can&apos;t get event from relay, ID: {id}</p>
<div className="mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl">
<h5 className="mb-1 text-sm font-medium text-red-500">Event not found</h5>
<div className="select-text break-all rounded border border-dashed border-red-500 p-2 text-red-500">
{id}
</div>
</div>
);
}

View File

@ -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[] }) {
</div>
</div>
) : (
<a
className="flex flex-col rounded-lg"
href={urls[0]}
<Link
to={urls[0]}
target="_blank"
rel="noreferrer"
className="flex flex-col rounded-lg"
>
{error ? (
<div className="flex flex-col gap-2 px-3 py-3">
@ -42,22 +44,22 @@ export function LinkPreview({ urls }: { urls: string[] }) {
className="h-44 w-full rounded-t-lg object-cover"
/>
)}
<div className="flex flex-col gap-2 px-3 py-3">
<h5 className="line-clamp-1 font-medium leading-none text-white">
<div className="flex flex-col gap-1 border-t border-white/5 px-3 py-3">
<h5 className="line-clamp-1 font-semibold leading-none text-white">
{data.title}
</h5>
{data.description && (
<p className="line-clamp-3 break-all text-sm text-white/50">
<p className="line-clamp-3 break-words text-sm text-white/50">
{data.description}
</p>
)}
<span className="mt-2.5 text-sm leading-none text-white/50">
<span className="mt-2.5 text-sm leading-none text-white/80">
{domain.hostname}
</span>
</div>
</>
)}
</a>
</Link>
)}
</div>
);

View File

@ -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,
}
);

View File

@ -45,6 +45,8 @@ export function parser(eventContent: string) {
// remove url from original content
word = word.replace(word, '');
}
content.links.push(url.toString());
}
// hashtag