implement view-source feature

This commit is contained in:
yggverse 2024-12-08 13:59:57 +02:00
parent 9784a8a1a1
commit 614aa1d71a
5 changed files with 86 additions and 25 deletions

View File

@ -95,8 +95,9 @@ GTK 4 / Libadwaita client written in Rust
* [ ] [NPS](https://nightfall.city/nps/info/specification.txt) * [ ] [NPS](https://nightfall.city/nps/info/specification.txt)
* [ ] Localhost * [ ] Localhost
* [ ] `file://` - localhost browser * [ ] `file://` - localhost browser
* [ ] `config://` - low-level key/value settings editor * [ ] System
* [ ] `view-source://` - page source viewer (where supported) * [ ] `config:` - low-level key/value settings editor
* [x] `view-source:` - page source viewer (where supported)
### Media types ### Media types

View File

@ -197,6 +197,12 @@ impl Page {
self.navigation.request.widget.entry.text() self.navigation.request.widget.entry.text()
}; };
// Detect source view mode, return `request` string prepared for route
let (request, is_view_source) = match request.strip_prefix("view-source:") {
Some(postfix) => (gformat!("{}", postfix.to_string()), true),
None => (request, false),
};
// Update // Update
self.meta.set_status(Status::Reload).set_title("Loading.."); self.meta.set_status(Status::Reload).set_title("Loading..");
self.browser_action.update.activate(Some(&id)); self.browser_action.update.activate(Some(&id));
@ -207,7 +213,7 @@ impl Page {
// Route by scheme // Route by scheme
match uri.scheme().as_str() { match uri.scheme().as_str() {
"file" => todo!(), "file" => todo!(),
"gemini" => self.load_gemini(uri, is_history), // @TODO "gemini" => self.load_gemini(uri, is_history, is_view_source), // @TODO
scheme => { scheme => {
// Define common data // Define common data
let status = Status::Failure; let status = Status::Failure;
@ -379,7 +385,7 @@ impl Page {
// Private helpers // Private helpers
// @TODO move outside // @TODO move outside
fn load_gemini(&self, uri: Uri, is_history: bool) { fn load_gemini(&self, uri: Uri, is_history: bool, is_view_source: bool) {
// Init shared clones // Init shared clones
let cancellable = self.client.cancellable(); let cancellable = self.client.cancellable();
let update = self.browser_action.update.clone(); let update = self.browser_action.update.clone();
@ -489,20 +495,26 @@ impl Page {
move |result|{ move |result|{
match result { match result {
Ok(buffer) => { Ok(buffer) => {
// Set children component // Set children component,
let text_gemini = content.to_text_gemini( // extract title from meta parsed
&uri, let title = if is_view_source {
content.to_text_source(
&buffer.data &buffer.data
); );
uri_to_title(&uri)
let title = match text_gemini.meta.title { } else {
Some(ref title) => title, match content.to_text_gemini(
None => &uri_to_title(&uri) &uri,
&buffer.data
).meta.title {
Some(meta_title) => meta_title,
None => uri_to_title(&uri)
}
}; };
// Update page meta // Update page meta
meta.set_status(Status::Success) meta.set_status(Status::Success)
.set_title(title); .set_title(&title);
// Update window components // Update window components
update.activate(Some(&id)); update.activate(Some(&id));

View File

@ -92,7 +92,7 @@ impl Content {
/// * could be useful to extract document title parsed from Gemtext /// * could be useful to extract document title parsed from Gemtext
pub fn to_text_gemini(&self, base: &Uri, data: &str) -> Text { pub fn to_text_gemini(&self, base: &Uri, data: &str) -> Text {
self.clean(); self.clean();
let text = Text::gemini( let text = Text::new_gemini(
data, data,
base, base,
(self.window_action.clone(), self.tab_action.clone()), (self.window_action.clone(), self.tab_action.clone()),
@ -101,6 +101,13 @@ impl Content {
text text
} }
pub fn to_text_source(&self, data: &str) -> Text {
self.clean();
let text = Text::new_source(data);
self.gobject.append(&text.scrolled_window);
text
}
/// Remove all children components from `Self` /// Remove all children components from `Self`
pub fn clean(&self) { pub fn clean(&self) {
while let Some(child) = self.gobject.last_child() { while let Some(child) = self.gobject.last_child() {

View File

@ -1,6 +1,8 @@
mod gemini; mod gemini;
mod source;
use gemini::Gemini; use gemini::Gemini;
use source::Source;
use crate::app::browser::window::{tab::item::Action as TabAction, Action as WindowAction}; use crate::app::browser::window::{tab::item::Action as TabAction, Action as WindowAction};
use gtk::{glib::Uri, ScrolledWindow}; use gtk::{glib::Uri, ScrolledWindow};
@ -16,24 +18,38 @@ pub struct Text {
} }
impl Text { impl Text {
// Construct // Constructors
pub fn gemini(gemtext: &str, base: &Uri, actions: (Rc<WindowAction>, Rc<TabAction>)) -> Self {
// Init components pub fn new_gemini(
gemtext: &str,
base: &Uri,
actions: (Rc<WindowAction>, Rc<TabAction>),
) -> Self {
// Init widget driver
let gemini = Gemini::new(gemtext, base, actions); let gemini = Gemini::new(gemtext, base, actions);
// Init meta // Init scrolled container
let meta = Meta {
title: gemini.reader.title.clone(),
};
// Init scrolled_window
let scrolled_window = ScrolledWindow::builder().build(); let scrolled_window = ScrolledWindow::builder().build();
scrolled_window.set_child(Some(&gemini.widget.clamp_scrollable)); scrolled_window.set_child(Some(&gemini.widget.clamp_scrollable));
// Result // Result
Self { Self {
meta, meta: Meta {
title: gemini.reader.title.clone(),
},
scrolled_window,
}
}
pub fn new_source(data: &str) -> Self {
// Init scrolled container
let scrolled_window = ScrolledWindow::builder().build();
scrolled_window.set_child(Some(&Source::new(data).text_view));
// Result
Self {
meta: Meta { title: None },
scrolled_window, scrolled_window,
} }
} }

View File

@ -0,0 +1,25 @@
use gtk::{TextBuffer, TextView};
const MARGIN: i32 = 8;
pub struct Source {
pub text_view: TextView,
}
impl Source {
pub fn new(data: &str) -> Self {
Self {
text_view: TextView::builder()
.bottom_margin(MARGIN)
.buffer(&TextBuffer::builder().text(data).build())
.cursor_visible(false)
.editable(false)
.left_margin(MARGIN)
.monospace(true)
.right_margin(MARGIN)
.top_margin(MARGIN)
.vexpand(true)
.build(),
}
}
}