Skip to content

Commit ccc2252

Browse files
frankfrank
authored andcommitted
feat(fetching): implement HTTP fetching and URL resolution for markdown files
1 parent cb4b94f commit ccc2252

File tree

6 files changed

+439
-15
lines changed

6 files changed

+439
-15
lines changed

PLAN.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,16 @@ bare/
169169
- Grunnleggende brukergrensesnitt
170170
- Automatisert bygg og test-pipeline
171171

172-
### Fase 2: Nettverksstøtte (2-3 uker)
172+
### Fase 2: Nettverksstøtte (2-3 uker) ✅ FULLFØRT
173173

174174
**Mål:** Hent markdown fra internett
175175

176-
- [ ] HTTP/HTTPS-klient med reqwest
177-
- [ ] Håndter Content-Type headers
178-
- [ ] Parse og gjør lenker klikkbare
179-
- [ ] Relative URL-oppløsning
180-
- [ ] Back/forward navigasjonshistorikk
181-
- [ ] Feilhåndtering (404, timeout, etc.)
176+
- [x] HTTP/HTTPS-klient med reqwest
177+
- [x] Håndter Content-Type headers
178+
- [x] Parse og gjør lenker klikkbare
179+
- [x] Relative URL-oppløsning
180+
- [x] Back/forward navigasjonshistorikk
181+
- [x] Feilhåndtering (404, timeout, etc.)
182182

183183
**Deliverables:**
184184
- App kan navigere til markdown-URLer

src-tauri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ tauri-plugin-fs = "2"
2222
serde = { version = "1", features = ["derive"] }
2323
serde_json = "1"
2424
pulldown-cmark = { version = "0.13", default-features = false, features = ["html", "simd"] }
25+
reqwest = { version = "0.12", features = ["rustls-tls"], default-features = false }
26+
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
27+
url = "2"
2528
thiserror = "2"
2629
log = "0.4"
2730
env_logger = "0.11"

src-tauri/src/commands.rs

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
//!
33
//! IPC-kommandoer som kan kalles fra frontend.
44
5+
use crate::fetcher::{self, Fetcher};
56
use crate::markdown;
67
use serde::{Deserialize, Serialize};
78
use std::fs;
89
use std::path::PathBuf;
10+
use std::sync::LazyLock;
11+
12+
/// Global HTTP-klient (gjenbrukes for alle forespørsler)
13+
static FETCHER: LazyLock<Fetcher> = LazyLock::new(Fetcher::new);
914

1015
/// Resultat fra markdown-rendering
1116
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -14,6 +19,12 @@ pub struct RenderedPage {
1419
pub html: String,
1520
/// Tittel ekstrahert fra markdown (hvis funnet)
1621
pub title: Option<String>,
22+
/// URL-en som ble brukt (etter eventuelle redirects)
23+
#[serde(skip_serializing_if = "Option::is_none")]
24+
pub url: Option<String>,
25+
/// Om innholdet ble hentet fra nettverket
26+
#[serde(default)]
27+
pub is_remote: bool,
1728
}
1829

1930
/// Rendrer markdown-tekst til HTML
@@ -28,7 +39,12 @@ pub fn render_markdown(content: String) -> RenderedPage {
2839
let html = markdown::render(&content);
2940
let title = markdown::extract_title(&content);
3041

31-
RenderedPage { html, title }
42+
RenderedPage {
43+
html,
44+
title,
45+
url: None,
46+
is_remote: false,
47+
}
3248
}
3349

3450
/// Åpner og leser en lokal markdown-fil
@@ -62,7 +78,56 @@ pub fn open_file(path: String) -> Result<RenderedPage, String> {
6278
let html = markdown::render(&content);
6379
let title = markdown::extract_title(&content);
6480

65-
Ok(RenderedPage { html, title })
81+
Ok(RenderedPage {
82+
html,
83+
title,
84+
url: Some(format!("file://{}", path.display())),
85+
is_remote: false,
86+
})
87+
}
88+
89+
/// Henter og rendrer markdown fra en URL
90+
///
91+
/// # Arguments
92+
/// * `url` - URL til markdown-filen som skal hentes
93+
///
94+
/// # Returns
95+
/// RenderedPage med HTML og tittel, eller feilmelding
96+
#[tauri::command]
97+
pub async fn fetch_url(url: String) -> Result<RenderedPage, String> {
98+
let result = FETCHER.fetch(&url).await.map_err(|e| e.to_string())?;
99+
100+
if !result.is_markdown {
101+
// For nå, returner en feilmelding for ikke-markdown innhold
102+
// I Fase 3 vil vi konvertere HTML til markdown
103+
return Err(format!(
104+
"Innholdet er ikke markdown (Content-Type: {:?}). HTML-konvertering kommer i en fremtidig versjon.",
105+
result.content_type
106+
));
107+
}
108+
109+
let html = markdown::render(&result.content);
110+
let title = markdown::extract_title(&result.content);
111+
112+
Ok(RenderedPage {
113+
html,
114+
title,
115+
url: Some(result.final_url),
116+
is_remote: true,
117+
})
118+
}
119+
120+
/// Løser en relativ URL mot en base-URL
121+
///
122+
/// # Arguments
123+
/// * `base_url` - Nåværende side sin URL
124+
/// * `relative_url` - Relativ URL som skal løses
125+
///
126+
/// # Returns
127+
/// Absolutt URL
128+
#[tauri::command]
129+
pub fn resolve_url(base_url: String, relative_url: String) -> Result<String, String> {
130+
fetcher::resolve_url(&base_url, &relative_url).map_err(|e| e.to_string())
66131
}
67132

68133
/// Returnerer velkomst-innhold for når appen starter
@@ -136,7 +201,12 @@ fn main() {
136201
let html = markdown::render(welcome_md);
137202
let title = markdown::extract_title(welcome_md);
138203

139-
RenderedPage { html, title }
204+
RenderedPage {
205+
html,
206+
title,
207+
url: None,
208+
is_remote: false,
209+
}
140210
}
141211

142212
#[cfg(test)]
@@ -148,12 +218,23 @@ mod tests {
148218
let result = render_markdown("# Test".to_string());
149219
assert!(result.html.contains("<h1>"));
150220
assert_eq!(result.title, Some("Test".to_string()));
221+
assert!(!result.is_remote);
151222
}
152223

153224
#[test]
154225
fn test_get_welcome_content() {
155226
let result = get_welcome_content();
156227
assert!(result.html.contains("Velkommen til Bare"));
157228
assert!(result.title.is_some());
229+
assert!(!result.is_remote);
230+
}
231+
232+
#[test]
233+
fn test_resolve_url_command() {
234+
let result = resolve_url(
235+
"https://example.com/docs/readme.md".to_string(),
236+
"other.md".to_string(),
237+
);
238+
assert_eq!(result.unwrap(), "https://example.com/docs/other.md");
158239
}
159240
}

0 commit comments

Comments
 (0)