diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 62a45f5..69c2d5f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -258,9 +258,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" dependencies = [ "serde", ] @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "dalet" -version = "1.0.0-pre6" +version = "1.0.0-pre9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc4c347533f8341633bd820799dea680f600e50891310b74bc914740681e8c2" +checksum = "2095f83b5256dc9a981639c3250aba53c02736a3601c1e6b2c54c27ec786274a" dependencies = [ "clap", "enum-procs", @@ -3093,6 +3093,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -3685,8 +3694,11 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] @@ -3704,6 +3716,17 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -4006,6 +4029,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" name = "vigi" version = "0.0.0" dependencies = [ + "bytes", "dalet", "mime", "reqwest 0.12.5", @@ -4013,7 +4037,9 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tokio", "tokio-gemini", + "tokio-rustls", "url", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6473151..e60670c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,7 +26,14 @@ tauri = { version = "1", features = [ ] } serde = { version = "1", features = ["derive"] } serde_json = "1" -dalet = "1.0.0-pre6" +dalet = "1.0.0-pre9" + +tokio = { version = "1.39.2", features = ["full"] } +tokio-rustls = { version = "0.26.0", default-features = false, features = [ + "ring", +] } + +bytes = "1.7.1" reqwest = "0.12.5" tokio-gemini = "0.1.0" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 64adb72..5cdd373 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,7 +7,7 @@ mod process_input; mod types; mod utils; -use tauri::async_runtime::Mutex; +use tokio::sync::Mutex; use types::{VigiError, VigiJsState, VigiState}; use utils::{read_or_create_jsonl, read_or_create_number}; diff --git a/src-tauri/src/process_input/insecure_gemini_client.rs b/src-tauri/src/process_input/insecure_gemini_client.rs new file mode 100644 index 0000000..1f0b107 --- /dev/null +++ b/src-tauri/src/process_input/insecure_gemini_client.rs @@ -0,0 +1,56 @@ +use tokio_rustls::rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + ClientConfig, SignatureScheme, +}; + +/// TODO: update to secure version when supported +pub fn insecure_gemini_client() -> tokio_gemini::Client { + tokio_gemini::Client::from( + ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(std::sync::Arc::new(NoCertVerification {})) + .with_no_client_auth(), + ) +} + +#[derive(Debug)] +struct NoCertVerification; + +impl ServerCertVerifier for NoCertVerification { + fn verify_server_cert( + &self, + _end_entity: &tokio_rustls::rustls::pki_types::CertificateDer<'_>, + _intermediates: &[tokio_rustls::rustls::pki_types::CertificateDer<'_>], + _server_name: &tokio_rustls::rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: tokio_rustls::rustls::pki_types::UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>, + _dss: &tokio_rustls::rustls::DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>, + _dss: &tokio_rustls::rustls::DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP521_SHA512, + ] + } +} diff --git a/src-tauri/src/process_input/mod.rs b/src-tauri/src/process_input/mod.rs index 097f254..a60cb07 100644 --- a/src-tauri/src/process_input/mod.rs +++ b/src-tauri/src/process_input/mod.rs @@ -1,15 +1,16 @@ use crate::types::{VigiError, VigiOutput}; +use bytes::Bytes; use mime::Mime; use url::Url; +mod insecure_gemini_client; mod process_data; mod process_url; use process_data::process_data; use process_url::process_url; -type Data = Vec; -type ReqResult = (Mime, Data); +type ReqResult = (Mime, Bytes); pub async fn process_input(input: &String) -> Result { let parsed = Url::parse(input); diff --git a/src-tauri/src/process_input/process_data.rs b/src-tauri/src/process_input/process_data.rs index 86eefc8..ba7cfd7 100644 --- a/src-tauri/src/process_input/process_data.rs +++ b/src-tauri/src/process_input/process_data.rs @@ -1,25 +1,41 @@ -use dalet::{daletl::ToDaletlPage, typed::Tag::*}; +use bytes::Bytes; +use dalet::{daletl::ToDaletlPage, parsers::gemtext::parse_gemtext, typed::Tag::*}; use mime::Mime; +use std::str; use crate::types::{VigiError, VigiOutput}; -pub async fn process_data(mime: Mime, data: Vec) -> Result { +pub async fn process_data(mime: Mime, data: Bytes) -> Result { let result = match (mime.type_().as_str(), mime.subtype().as_str()) { ("text", "plain") => { - process_text(String::from_utf8(data).map_err(|_| VigiError::TextIsNotUtf8)?).await + process_text(str::from_utf8(&data).map_err(|_| VigiError::InvalidCharset)?).await + } + ("text", "gemini") => { + process_gemini(str::from_utf8(&data).map_err(|_| VigiError::InvalidCharset)?).await? } - // ("text", "gemini") => { - // process_text(String::from_utf8(data).map_err(|_| VigiError::TextIsNotUtf8)?).await - // } _ => Err(VigiError::UnsupportedMimeType)?, }; Ok(result) } -async fn process_text(data: String) -> VigiOutput { - let mut truncated = data.clone(); +async fn process_text(data: &str) -> VigiOutput { + let mut truncated = data.to_owned(); truncated.truncate(50); VigiOutput::new(truncated, vec![El(data.into())].to_dl_page()) } + +async fn process_gemini(data: &str) -> Result { + let mut truncated = data.to_owned(); + truncated.truncate(50); + + let res = VigiOutput::new( + truncated, + parse_gemtext(data) + .map_err(|_| VigiError::Parse)? + .to_dl_page(), + ); + + Ok(res) +} diff --git a/src-tauri/src/process_input/process_url.rs b/src-tauri/src/process_input/process_url.rs index f45c916..292cd0d 100644 --- a/src-tauri/src/process_input/process_url.rs +++ b/src-tauri/src/process_input/process_url.rs @@ -1,15 +1,17 @@ +use bytes::Bytes; use mime::Mime; use reqwest::header::CONTENT_TYPE; +use tokio::io::AsyncReadExt; use url::Url; use crate::types::VigiError; -use super::ReqResult; +use super::{insecure_gemini_client, ReqResult}; pub async fn process_url(url: Url) -> Result { let result = match url.scheme() { "http" | "https" => process_http(url.to_string()).await?, - // "gemini" => process_gemini(url.to_string()).await?, + "gemini" => process_gemini(url.to_string()).await?, _ => Err(VigiError::UnsupportedProtocol)?, }; @@ -37,16 +39,21 @@ async fn process_http(url: String) -> Result { )) } -// async fn process_gemini(url: String) -> Result { -// let res = tokio_gemini::Client::default() -// .request(&url) -// .await -// .map_err(|e| { -// println!("{:#?}", e); -// VigiError::Network -// })?; +async fn process_gemini(url: String) -> Result { + let mut res = insecure_gemini_client::insecure_gemini_client() + .request(&url) + .await + .map_err(|_| VigiError::Network)?; -// let mime_type = res.mime().map_err(|_| VigiError::InvalidMimeType)?; + let mime_type = res.mime().map_err(|_| VigiError::InvalidMimeType)?; -// Ok((mime_type, res.message().as_bytes().into())) -// } + let mut buffer = Vec::new(); + + let tls_stream = res.body(); + tls_stream + .read_to_end(&mut buffer) + .await + .map_err(|_| VigiError::Network)?; + + Ok((mime_type, Bytes::from(buffer).into())) +} diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index c65eaaa..2bab929 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -21,7 +21,7 @@ pub enum VigiError { UnsupportedMimeType, InvalidMimeType, - TextIsNotUtf8, + InvalidCharset, } #[derive(Debug, Clone)] diff --git a/src/app.css b/src/app.css index bf2cf2e..faa769c 100644 --- a/src/app.css +++ b/src/app.css @@ -1,3 +1,5 @@ +@import "tags.css"; + @tailwind base; @tailwind components; @tailwind utilities; @@ -12,14 +14,14 @@ --min-bg: #96e28a; --max-bg: #193815; - --min-text: #bef5b5; + --min-text: #82de73; --max-text: #fff; /* Dark */ /* --min-bg: #555; --max-bg: #000; - --min-text: #cfcfcf; + --min-text: #555; --max-text: #fff; */ } @@ -27,7 +29,7 @@ /* Components */ body { - @apply color-vigi-90 cursor-default; + @apply color-vigi-90 cursor-default overflow-clip; user-select: none; } @@ -43,11 +45,11 @@ body { } .main-window { - @apply grow flex flex-col gap-3; + @apply grow flex flex-col gap-3 w-3/4; } .browser-window { - @apply grow overflow-auto text-wrap select-text cursor-auto; + @apply grow overflow-y-auto text-wrap select-text cursor-auto overflow-x-hidden; } .window-controls { @@ -110,7 +112,7 @@ body { } .search-input { - @apply px-2 py-1 rounded-xl grow; + @apply px-2 py-1 rounded-xl grow min-w-0; @apply color-vigi-60 outline-none; @apply focus:color-vigi-50 hover:color-vigi-55; @@ -160,24 +162,26 @@ input::placeholder { @apply color-vigi-60; } -/* width */ ::-webkit-scrollbar { width: 15px; + height: 15px; } -/* Track */ ::-webkit-scrollbar-track { @apply bg-transparent; } -/* Handle */ ::-webkit-scrollbar-thumb { @apply rounded-xl color-vigi-70 bg-clip-content; border: 6px solid transparent; } -/* Handle on hover */ ::-webkit-scrollbar-thumb:hover { @apply color-vigi-75; border: 5px solid transparent; } + +::-webkit-scrollbar-thumb:active { + @apply color-vigi-100; + border: 4px solid transparent; +} diff --git a/src/lib/components/BrowserWindow.svelte b/src/lib/components/BrowserWindow.svelte index 3dada41..7885485 100644 --- a/src/lib/components/BrowserWindow.svelte +++ b/src/lib/components/BrowserWindow.svelte @@ -1,40 +1,40 @@ - {#if loading} -
- -
- {/if} + {#if loading} +
+ +
+ {/if} - +
diff --git a/src/lib/components/DaletlRenderer/BodyRenderer.svelte b/src/lib/components/DaletlRenderer/BodyRenderer.svelte index 0dcda38..95e18c1 100644 --- a/src/lib/components/DaletlRenderer/BodyRenderer.svelte +++ b/src/lib/components/DaletlRenderer/BodyRenderer.svelte @@ -1,15 +1,21 @@ {#if typeof body === "string"} - + {#each body.split("\n") as line} + {line}
+ {/each} {:else if body !== null} - {#each body as tag} - - {/each} + {#each body as tag} + + {/each} +{/if} + +{#if body === null && ifNull} + {ifNull} {/if} diff --git a/src/lib/components/DaletlRenderer/TagRenderer.svelte b/src/lib/components/DaletlRenderer/TagRenderer.svelte index 942494f..1efad95 100644 --- a/src/lib/components/DaletlRenderer/TagRenderer.svelte +++ b/src/lib/components/DaletlRenderer/TagRenderer.svelte @@ -1,10 +1,43 @@ {#if tag.id === 0} - + +{:else if tag.id === 1} + +{:else if tag.id === 2} + +{:else if tag.id === 3} + +{:else if tag.id === 4} + +{:else if tag.id === 7} + +{:else if tag.id === 8} + +{:else if tag.id === 9} + diff --git a/src/lib/components/DaletlRenderer/tags/Code.svelte b/src/lib/components/DaletlRenderer/tags/Code.svelte new file mode 100644 index 0000000..7273a98 --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/Code.svelte @@ -0,0 +1,11 @@ + + +
{body}
diff --git a/src/lib/components/DaletlRenderer/tags/Element.svelte b/src/lib/components/DaletlRenderer/tags/Element.svelte index 8dde4a5..348ea12 100644 --- a/src/lib/components/DaletlRenderer/tags/Element.svelte +++ b/src/lib/components/DaletlRenderer/tags/Element.svelte @@ -1,16 +1,10 @@
- {#if typeof body === "string"} - {#each body.split("\n") as line} - {line}
- {/each} - {:else} - - {/if} +
diff --git a/src/lib/components/DaletlRenderer/tags/Heading.svelte b/src/lib/components/DaletlRenderer/tags/Heading.svelte new file mode 100644 index 0000000..84225c8 --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/Heading.svelte @@ -0,0 +1,23 @@ + + +{#if argument === 2} +

{body}

+{:else if argument === 3} +

{body}

+{:else if argument === 4} +

{body}

+{:else if argument === 5} +
{body}
+{:else if argument === 6} +
{body}
+{:else} +

{body}

+{/if} diff --git a/src/lib/components/DaletlRenderer/tags/LineBreak.svelte b/src/lib/components/DaletlRenderer/tags/LineBreak.svelte new file mode 100644 index 0000000..3fc714a --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/LineBreak.svelte @@ -0,0 +1 @@ +
diff --git a/src/lib/components/DaletlRenderer/tags/Link.svelte b/src/lib/components/DaletlRenderer/tags/Link.svelte new file mode 100644 index 0000000..ce7a6bd --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/Link.svelte @@ -0,0 +1,10 @@ + + + diff --git a/src/lib/components/DaletlRenderer/tags/NavButton.svelte b/src/lib/components/DaletlRenderer/tags/NavButton.svelte new file mode 100644 index 0000000..7cf4ebc --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/NavButton.svelte @@ -0,0 +1,10 @@ + + + diff --git a/src/lib/components/DaletlRenderer/tags/NavLink.svelte b/src/lib/components/DaletlRenderer/tags/NavLink.svelte new file mode 100644 index 0000000..1f50e9b --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/NavLink.svelte @@ -0,0 +1,10 @@ + + + diff --git a/src/lib/components/DaletlRenderer/tags/Paragraph.svelte b/src/lib/components/DaletlRenderer/tags/Paragraph.svelte new file mode 100644 index 0000000..ce03b93 --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/Paragraph.svelte @@ -0,0 +1,10 @@ + + +

+ +

diff --git a/src/lib/components/DaletlRenderer/tags/Pre.svelte b/src/lib/components/DaletlRenderer/tags/Pre.svelte new file mode 100644 index 0000000..6e01d82 --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/Pre.svelte @@ -0,0 +1,9 @@ + + +
{body}
diff --git a/src/lib/components/DaletlRenderer/tags/UnorderedList.svelte b/src/lib/components/DaletlRenderer/tags/UnorderedList.svelte new file mode 100644 index 0000000..4fc61eb --- /dev/null +++ b/src/lib/components/DaletlRenderer/tags/UnorderedList.svelte @@ -0,0 +1,15 @@ + + +
    + {#each body as tag} +
  • + {/each} +
diff --git a/src/tags.css b/src/tags.css new file mode 100644 index 0000000..040e2c4 --- /dev/null +++ b/src/tags.css @@ -0,0 +1,65 @@ +.headings { + @apply font-bold my-4; +} + +.h1 { + @apply text-3xl headings; +} + +.h2 { + @apply text-2xl headings; +} + +.h3 { + @apply text-xl headings; +} + +.h4 { + @apply text-lg headings; +} + +.h5 { + @apply text-base headings; +} + +.h6 { + @apply text-sm headings; +} + +.p { + @apply my-3; +} + +.btn, +.navbtn { + @apply p-1 rounded-lg; + @apply ease-out duration-150; + + @apply hover:color-vigi-90; + @apply cursor-pointer; + + @apply color-vigi-70 active:color-vigi-100; +} + +.link, +.navlink { + @apply underline font-semibold; + /* @apply text-bg-vigi-90 hover:text-bg-vigi-95 active:text-bg-vigi-100; */ + @apply text-vigi-30 hover:text-vigi-20 active:text-vigi-0; +} + +.ul { + @apply list-disc list-inside ms-3; +} + +.pre { + @apply font-mono; +} + +.code { + @apply color-vigi-50 p-4 rounded-xl overflow-auto; +} + +.bq { + @apply color-vigi-50 p-4 rounded-xl border-s-4 text-wrap; +} diff --git a/tailwind.config.js b/tailwind.config.js index 66389a4..3e88456 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -13,10 +13,31 @@ export default { plugins: [ plugin(({ addUtilities }) => { let utilities = {}; + for (let i = 0; i <= 100; i += 5) { + let text = `color-mix(in var(--colorspace), var(--max-text) ${i}%, var(--min-text))`; + let bg = `color-mix(in var(--colorspace), var(--max-bg) ${i}%, var(--min-bg))`; + utilities[`.color-vigi-${i}`] = { - color: `color-mix(in var(--colorspace), var(--max-text) ${i}%, var(--min-text))`, - "background-color": `color-mix(in var(--colorspace), var(--max-bg) ${i}%, var(--min-bg))`, + color: text, + "background-color": bg, + "border-color": text, + }; + + utilities[`.text-vigi-${i}`] = { + color: text, + }; + + utilities[`.text-bg-vigi-${i}`] = { + color: bg, + }; + + utilities[`.bg-vigi-${i}`] = { + "background-color": bg, + }; + + utilities[`.bg-text-vigi-${i}`] = { + "background-color": text, }; }