majc: render gemini pages
This commit is contained in:
parent
479fb697ee
commit
d96a7a85bf
|
@ -1,22 +1,24 @@
|
|||
# majc
|
||||
|
||||
```
|
||||
__
|
||||
_____ _____ |__| ____
|
||||
/ \ \__ \ | |_/ ___\
|
||||
| Y Y \ / __ \_ | |\ \___
|
||||
|__|_| /(____ //\__| | \___ >
|
||||
\/ \/ \______| \/
|
||||
```
|
||||
|
||||
A curses client for Gemini!
|
||||
|
||||
=> gemini://gemini.circumlunar.space/ Gemini homepage
|
||||
|
||||
## Homepage
|
||||
|
||||
The main homepage for majc is on tulpa.dev:
|
||||
|
||||
=> https://tulpa.dev/cadey/maj
|
||||
|
||||
## Important Keys
|
||||
|
||||
<esc>: opens the menubar
|
||||
c: closes the active window
|
||||
o: prompts to open a URL
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use cursive::{
|
||||
event::Key,
|
||||
menu::MenuTree,
|
||||
theme::{BaseColor, Color, Effect, Style},
|
||||
traits::*,
|
||||
utils::markup::StyledString,
|
||||
views::{Dialog, EditView, Panel, ResizedView, TextView},
|
||||
Cursive,
|
||||
};
|
||||
|
@ -51,7 +53,7 @@ fn help(siv: &mut Cursive) {
|
|||
let content = include_str!("./help.gmi");
|
||||
|
||||
siv.add_layer(
|
||||
Dialog::around(Panel::new(TextView::new(content).scrollable()))
|
||||
Dialog::around(Panel::new(TextView::new(render_gemini(content)).scrollable()))
|
||||
.title("Help")
|
||||
.dismiss_button("Ok"),
|
||||
);
|
||||
|
@ -94,23 +96,69 @@ fn open(siv: &mut Cursive, url: &str) {
|
|||
}
|
||||
|
||||
fn show(siv: &mut Cursive, url: &str, resp: Response) {
|
||||
if resp.status != StatusCode::Success {
|
||||
siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta)));
|
||||
return;
|
||||
}
|
||||
use StatusCode::*;
|
||||
|
||||
match str::from_utf8(&resp.body) {
|
||||
Ok(content) => {
|
||||
siv.add_fullscreen_layer(ResizedView::with_full_screen(
|
||||
Dialog::around(TextView::new(content).scrollable())
|
||||
.title(format!("{}: {}", url, resp.meta)),
|
||||
));
|
||||
match resp.status {
|
||||
Success => {
|
||||
match str::from_utf8(&resp.body) {
|
||||
Ok(content) => {
|
||||
siv.add_fullscreen_layer(ResizedView::with_full_screen(
|
||||
Dialog::around(TextView::new(render_gemini(content)).scrollable())
|
||||
.title(format!("{}: {}", url, resp.meta)),
|
||||
));
|
||||
}
|
||||
Err(why) => {
|
||||
siv.add_layer(Dialog::info(format!(
|
||||
"UTF/8 decoding error for {}: {:?}",
|
||||
url, why
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
siv.add_layer(Dialog::info(format!(
|
||||
"UTF/8 decoding error for {}: {:?}",
|
||||
url, why
|
||||
)));
|
||||
|
||||
TemporaryRedirect => {
|
||||
open(siv, resp.meta.as_str());
|
||||
}
|
||||
|
||||
PermanentRedirect => {
|
||||
open(siv, resp.meta.as_str());
|
||||
}
|
||||
|
||||
_ => {
|
||||
siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_gemini(body: &str) -> StyledString {
|
||||
let doc = maj::gemini::parse(body);
|
||||
let mut styled = StyledString::new();
|
||||
|
||||
use maj::gemini::Node::*;
|
||||
|
||||
for node in doc {
|
||||
match node {
|
||||
Text(line) => styled.append(StyledString::plain(line)),
|
||||
Link { to, name } => match name {
|
||||
None => styled.append(StyledString::styled(
|
||||
to,
|
||||
Style::from(Effect::Underline),
|
||||
)),
|
||||
Some(name) => styled.append(StyledString::styled(
|
||||
format!("{}: {}", to, name),
|
||||
Style::from(Effect::Underline),
|
||||
)),
|
||||
},
|
||||
Preformatted(data) => styled.append(StyledString::plain(data)),
|
||||
Heading { level: _, body } => {
|
||||
styled.append(StyledString::styled(body, Style::from(Effect::Bold)))
|
||||
}
|
||||
ListItem(item) => styled.append(StyledString::plain(format!("* {}", item))),
|
||||
Quote(quote) => styled.append(StyledString::plain(format!("> {}", quote))),
|
||||
}
|
||||
styled.append(StyledString::plain("\n"));
|
||||
}
|
||||
|
||||
styled
|
||||
}
|
||||
|
|
|
@ -93,7 +93,10 @@ pub fn parse(doc: &str) -> Vec<Node> {
|
|||
collect_preformatted = !collect_preformatted;
|
||||
if !collect_preformatted {
|
||||
result.push(Node::Preformatted(
|
||||
String::from_utf8(preformatted_buffer).unwrap(),
|
||||
String::from_utf8(preformatted_buffer)
|
||||
.unwrap()
|
||||
.trim_end()
|
||||
.to_string(),
|
||||
));
|
||||
preformatted_buffer = vec![];
|
||||
}
|
||||
|
@ -145,8 +148,14 @@ pub fn parse(doc: &str) -> Vec<Node> {
|
|||
let sp = line[2..].split_ascii_whitespace().collect::<Vec<&str>>();
|
||||
|
||||
match sp.len() {
|
||||
1 => result.push(Node::Link { to: sp[0].trim().to_string(), name: None }),
|
||||
_ => result.push(Node::Link { to: sp[0].trim().to_string(), name: Some(sp[1..].join(" ").trim().to_string()) }),
|
||||
1 => result.push(Node::Link {
|
||||
to: sp[0].trim().to_string(),
|
||||
name: None,
|
||||
}),
|
||||
_ => result.push(Node::Link {
|
||||
to: sp[0].trim().to_string(),
|
||||
name: Some(sp[1..].join(" ").trim().to_string()),
|
||||
}),
|
||||
}
|
||||
|
||||
continue;
|
||||
|
@ -194,7 +203,7 @@ mod tests {
|
|||
\n\
|
||||
Test\n";
|
||||
let expected: Vec<Node> = vec![
|
||||
Node::Preformatted("hi there\n".to_string()),
|
||||
Node::Preformatted("hi there".to_string()),
|
||||
Node::Text(String::new()),
|
||||
Node::Text("Test".to_string()),
|
||||
];
|
||||
|
@ -227,8 +236,14 @@ mod tests {
|
|||
let _ = pretty_env_logger::try_init();
|
||||
let msg = "=>/\n=> / Go home";
|
||||
let expected: Vec<Node> = vec![
|
||||
Node::Link{to: "/".to_string(), name: None},
|
||||
Node::Link{to: "/".to_string(), name: Some("Go home".to_string()) },
|
||||
Node::Link {
|
||||
to: "/".to_string(),
|
||||
name: None,
|
||||
},
|
||||
Node::Link {
|
||||
to: "/".to_string(),
|
||||
name: Some("Go home".to_string()),
|
||||
},
|
||||
];
|
||||
assert_eq!(expected, parse(msg));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue