mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-03-31 08:35:28 +00:00
implement copy link text, selected text, add link to the bookmarks (context menu) items; group menu items
This commit is contained in:
parent
c95cb6e756
commit
bb08b7cb9a
7 changed files with 295 additions and 77 deletions
|
|
@ -21,7 +21,7 @@ impl Text {
|
||||||
.info
|
.info
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.set_mime(Some("text/gemini".to_string()));
|
.set_mime(Some("text/gemini".to_string()));
|
||||||
page.content.to_text_gemini(uri, data)
|
page.content.to_text_gemini(&page.profile, uri, data)
|
||||||
}),
|
}),
|
||||||
Self::Markdown(uri, data) => (uri, {
|
Self::Markdown(uri, data) => (uri, {
|
||||||
page.navigation
|
page.navigation
|
||||||
|
|
@ -29,7 +29,7 @@ impl Text {
|
||||||
.info
|
.info
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.set_mime(Some("text/markdown".to_string()));
|
.set_mime(Some("text/markdown".to_string()));
|
||||||
page.content.to_text_markdown(uri, data)
|
page.content.to_text_markdown(&page.profile, uri, data)
|
||||||
}),
|
}),
|
||||||
Self::Plain(uri, data) => (uri, page.content.to_text_plain(data)),
|
Self::Plain(uri, data) => (uri, page.content.to_text_plain(data)),
|
||||||
Self::Source(uri, data) => (uri, page.content.to_text_source(data)),
|
Self::Source(uri, data) => (uri, page.content.to_text_source(data)),
|
||||||
|
|
|
||||||
|
|
@ -357,8 +357,8 @@ fn handle(
|
||||||
page.content.to_text_source(data)
|
page.content.to_text_source(data)
|
||||||
} else {
|
} else {
|
||||||
match m.as_str() {
|
match m.as_str() {
|
||||||
"text/gemini" => page.content.to_text_gemini(&uri, data),
|
"text/gemini" => page.content.to_text_gemini(&page.profile, &uri, data),
|
||||||
"text/markdown" => page.content.to_text_markdown(&uri, data),
|
"text/markdown" => page.content.to_text_markdown(&page.profile, &uri, data),
|
||||||
"text/plain" => page.content.to_text_plain(data),
|
"text/plain" => page.content.to_text_plain(data),
|
||||||
_ => panic!() // unexpected
|
_ => panic!() // unexpected
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,7 @@ fn render(
|
||||||
} else if q.ends_with("/") {
|
} else if q.ends_with("/") {
|
||||||
p.content.to_text_nex(&u, d)
|
p.content.to_text_nex(&u, d)
|
||||||
} else if q.ends_with(".gmi") || q.ends_with(".gemini") {
|
} else if q.ends_with(".gmi") || q.ends_with(".gemini") {
|
||||||
p.content.to_text_gemini(&u, d)
|
p.content.to_text_gemini(&p.profile, &u, d)
|
||||||
} else {
|
} else {
|
||||||
p.content.to_text_plain(d)
|
p.content.to_text_plain(d)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use directory::Directory;
|
||||||
use image::Image;
|
use image::Image;
|
||||||
use text::Text;
|
use text::Text;
|
||||||
|
|
||||||
|
use crate::profile::Profile;
|
||||||
|
|
||||||
use super::{ItemAction, TabAction, WindowAction};
|
use super::{ItemAction, TabAction, WindowAction};
|
||||||
use adw::StatusPage;
|
use adw::StatusPage;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
|
|
@ -126,9 +128,14 @@ impl Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `text/gemini`
|
/// `text/gemini`
|
||||||
pub fn to_text_gemini(&self, base: &Uri, data: &str) -> Text {
|
pub fn to_text_gemini(&self, profile: &Rc<Profile>, base: &Uri, data: &str) -> Text {
|
||||||
self.clean();
|
self.clean();
|
||||||
match Text::gemini((&self.window_action, &self.item_action), base, data) {
|
match Text::gemini(
|
||||||
|
(&self.window_action, &self.item_action),
|
||||||
|
profile,
|
||||||
|
base,
|
||||||
|
data,
|
||||||
|
) {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
self.g_box.append(&text.scrolled_window);
|
self.g_box.append(&text.scrolled_window);
|
||||||
text
|
text
|
||||||
|
|
@ -155,9 +162,14 @@ impl Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `text/markdown`
|
/// `text/markdown`
|
||||||
pub fn to_text_markdown(&self, base: &Uri, data: &str) -> Text {
|
pub fn to_text_markdown(&self, profile: &Rc<Profile>, base: &Uri, data: &str) -> Text {
|
||||||
self.clean();
|
self.clean();
|
||||||
let m = Text::markdown((&self.window_action, &self.item_action), base, data);
|
let m = Text::markdown(
|
||||||
|
(&self.window_action, &self.item_action),
|
||||||
|
profile,
|
||||||
|
base,
|
||||||
|
data,
|
||||||
|
);
|
||||||
self.g_box.append(&m.scrolled_window);
|
self.g_box.append(&m.scrolled_window);
|
||||||
m
|
m
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ mod nex;
|
||||||
mod plain;
|
mod plain;
|
||||||
mod source;
|
mod source;
|
||||||
|
|
||||||
|
use crate::profile::Profile;
|
||||||
|
|
||||||
use super::{ItemAction, WindowAction};
|
use super::{ItemAction, WindowAction};
|
||||||
use adw::ClampScrollable;
|
use adw::ClampScrollable;
|
||||||
use gemini::Gemini;
|
use gemini::Gemini;
|
||||||
|
|
@ -27,10 +29,11 @@ pub struct Text {
|
||||||
impl Text {
|
impl Text {
|
||||||
pub fn gemini(
|
pub fn gemini(
|
||||||
actions: (&Rc<WindowAction>, &Rc<ItemAction>),
|
actions: (&Rc<WindowAction>, &Rc<ItemAction>),
|
||||||
|
profile: &Rc<Profile>,
|
||||||
base: &Uri,
|
base: &Uri,
|
||||||
gemtext: &str,
|
gemtext: &str,
|
||||||
) -> Result<Self, (String, Option<Self>)> {
|
) -> Result<Self, (String, Option<Self>)> {
|
||||||
match Gemini::build(actions, base, gemtext) {
|
match Gemini::build(actions, profile, base, gemtext) {
|
||||||
Ok(widget) => Ok(Self {
|
Ok(widget) => Ok(Self {
|
||||||
scrolled_window: reader(&widget.text_view),
|
scrolled_window: reader(&widget.text_view),
|
||||||
text_view: widget.text_view,
|
text_view: widget.text_view,
|
||||||
|
|
@ -55,10 +58,11 @@ impl Text {
|
||||||
|
|
||||||
pub fn markdown(
|
pub fn markdown(
|
||||||
actions: (&Rc<WindowAction>, &Rc<ItemAction>),
|
actions: (&Rc<WindowAction>, &Rc<ItemAction>),
|
||||||
|
profile: &Rc<Profile>,
|
||||||
base: &Uri,
|
base: &Uri,
|
||||||
gemtext: &str,
|
gemtext: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let markdown = Markdown::build(actions, base, gemtext);
|
let markdown = Markdown::build(actions, profile, base, gemtext);
|
||||||
Self {
|
Self {
|
||||||
scrolled_window: reader(&markdown.text_view),
|
scrolled_window: reader(&markdown.text_view),
|
||||||
text_view: markdown.text_view,
|
text_view: markdown.text_view,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ mod syntax;
|
||||||
mod tag;
|
mod tag;
|
||||||
|
|
||||||
use super::{ItemAction, WindowAction};
|
use super::{ItemAction, WindowAction};
|
||||||
use crate::app::browser::window::action::Position;
|
use crate::{app::browser::window::action::Position, profile::Profile};
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
EventControllerMotion, GestureClick, TextBuffer, TextTag, TextView, TextWindowType,
|
EventControllerMotion, GestureClick, TextBuffer, TextTag, TextView, TextWindowType,
|
||||||
UriLauncher, Window, WrapMode,
|
UriLauncher, Window, WrapMode,
|
||||||
gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY, BUTTON_SECONDARY, RGBA},
|
gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY, BUTTON_SECONDARY, Display, RGBA},
|
||||||
gio::{Cancellable, SimpleAction, SimpleActionGroup},
|
gio::{Cancellable, Menu, SimpleAction, SimpleActionGroup},
|
||||||
glib::{Uri, uuid_string_random},
|
glib::{Uri, uuid_string_random},
|
||||||
prelude::{PopoverExt, TextBufferExt, TextBufferExtManual, TextTagExt, TextViewExt, WidgetExt},
|
prelude::{PopoverExt, TextBufferExt, TextBufferExtManual, TextTagExt, TextViewExt, WidgetExt},
|
||||||
};
|
};
|
||||||
|
|
@ -36,6 +36,7 @@ impl Gemini {
|
||||||
/// Build new `Self`
|
/// Build new `Self`
|
||||||
pub fn build(
|
pub fn build(
|
||||||
(window_action, item_action): (&Rc<WindowAction>, &Rc<ItemAction>),
|
(window_action, item_action): (&Rc<WindowAction>, &Rc<ItemAction>),
|
||||||
|
profile: &Rc<Profile>,
|
||||||
base: &Uri,
|
base: &Uri,
|
||||||
gemtext: &str,
|
gemtext: &str,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
|
@ -220,7 +221,7 @@ impl Gemini {
|
||||||
let mut alt = Vec::with_capacity(2);
|
let mut alt = Vec::with_capacity(2);
|
||||||
|
|
||||||
if uri.scheme() != base.scheme() {
|
if uri.scheme() != base.scheme() {
|
||||||
alt.push("⇖".to_string());
|
alt.push(LINK_EXTERNAL_INDICATOR.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
alt.push(match link.alt {
|
alt.push(match link.alt {
|
||||||
|
|
@ -235,9 +236,7 @@ impl Gemini {
|
||||||
.wrap_mode(WrapMode::Word)
|
.wrap_mode(WrapMode::Word)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if !tag.text_tag_table.add(&a) {
|
assert!(tag.text_tag_table.add(&a));
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.insert_with_tags(&mut buffer.end_iter(), &alt.join(" "), &[&a]);
|
buffer.insert_with_tags(&mut buffer.end_iter(), &alt.join(" "), &[&a]);
|
||||||
buffer.insert(&mut buffer.end_iter(), NEW_LINE);
|
buffer.insert(&mut buffer.end_iter(), NEW_LINE);
|
||||||
|
|
@ -296,14 +295,39 @@ impl Gemini {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let action_link_copy =
|
let action_link_copy_url =
|
||||||
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
action_link_copy.connect_activate(|this, _| {
|
action_link_copy_url.connect_activate(|this, _| {
|
||||||
gtk::gdk::Display::default()
|
Display::default()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clipboard()
|
.clipboard()
|
||||||
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
||||||
});
|
});
|
||||||
|
let action_link_copy_text =
|
||||||
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
|
action_link_copy_text.connect_activate(|this, _| {
|
||||||
|
Display::default()
|
||||||
|
.unwrap()
|
||||||
|
.clipboard()
|
||||||
|
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
||||||
|
});
|
||||||
|
let action_link_copy_text_selected =
|
||||||
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
|
action_link_copy_text_selected.connect_activate(|this, _| {
|
||||||
|
Display::default()
|
||||||
|
.unwrap()
|
||||||
|
.clipboard()
|
||||||
|
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
||||||
|
});
|
||||||
|
let action_link_bookmark =
|
||||||
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
|
action_link_bookmark.connect_activate({
|
||||||
|
let p = profile.clone();
|
||||||
|
move |this, _| {
|
||||||
|
let state = this.state().unwrap().get::<String>().unwrap();
|
||||||
|
p.bookmark.toggle(&state, None).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
let action_link_download =
|
let action_link_download =
|
||||||
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
action_link_download.connect_activate({
|
action_link_download.connect_activate({
|
||||||
|
|
@ -338,14 +362,17 @@ impl Gemini {
|
||||||
Some(&{
|
Some(&{
|
||||||
let g = SimpleActionGroup::new();
|
let g = SimpleActionGroup::new();
|
||||||
g.add_action(&action_link_tab);
|
g.add_action(&action_link_tab);
|
||||||
g.add_action(&action_link_copy);
|
g.add_action(&action_link_copy_url);
|
||||||
|
g.add_action(&action_link_copy_text);
|
||||||
|
g.add_action(&action_link_copy_text_selected);
|
||||||
|
g.add_action(&action_link_bookmark);
|
||||||
g.add_action(&action_link_download);
|
g.add_action(&action_link_download);
|
||||||
g.add_action(&action_link_source);
|
g.add_action(&action_link_source);
|
||||||
g
|
g
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let link_context = gtk::PopoverMenu::from_model(Some(&{
|
let link_context = gtk::PopoverMenu::from_model(Some(&{
|
||||||
let m = gtk::gio::Menu::new();
|
let m = Menu::new();
|
||||||
m.append(
|
m.append(
|
||||||
Some("Open Link in New Tab"),
|
Some("Open Link in New Tab"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
|
|
@ -353,27 +380,56 @@ impl Gemini {
|
||||||
action_link_tab.name()
|
action_link_tab.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
m.append(
|
m.append_section(None, &{
|
||||||
Some("Copy Link"),
|
let m_copy = Menu::new();
|
||||||
|
m_copy.append(
|
||||||
|
Some("Copy Link URL"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"{link_context_group_id}.{}",
|
"{link_context_group_id}.{}",
|
||||||
action_link_copy.name()
|
action_link_copy_url.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
m.append(
|
m_copy.append(
|
||||||
|
Some("Copy Link Text"),
|
||||||
|
Some(&format!(
|
||||||
|
"{link_context_group_id}.{}",
|
||||||
|
action_link_copy_text.name()
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
m_copy.append(
|
||||||
|
Some("Copy Link Text Selected"),
|
||||||
|
Some(&format!(
|
||||||
|
"{link_context_group_id}.{}",
|
||||||
|
action_link_copy_text_selected.name()
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
m_copy
|
||||||
|
});
|
||||||
|
m.append_section(None, &{
|
||||||
|
let m_other = Menu::new();
|
||||||
|
m_other.append(
|
||||||
|
Some("Bookmark Link"), // @TODO highlight state
|
||||||
|
Some(&format!(
|
||||||
|
"{link_context_group_id}.{}",
|
||||||
|
action_link_bookmark.name()
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
m_other.append(
|
||||||
Some("Download Link"),
|
Some("Download Link"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"{link_context_group_id}.{}",
|
"{link_context_group_id}.{}",
|
||||||
action_link_download.name()
|
action_link_download.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
m.append(
|
m_other.append(
|
||||||
Some("View Link as Source"),
|
Some("View Link as Source"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"{link_context_group_id}.{}",
|
"{link_context_group_id}.{}",
|
||||||
action_link_source.name()
|
action_link_source.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
m_other
|
||||||
|
});
|
||||||
m
|
m
|
||||||
}));
|
}));
|
||||||
link_context.set_parent(&text_view);
|
link_context.set_parent(&text_view);
|
||||||
|
|
@ -435,18 +491,61 @@ impl Gemini {
|
||||||
let request_str = uri.to_str();
|
let request_str = uri.to_str();
|
||||||
let request_var = request_str.to_variant();
|
let request_var = request_str.to_variant();
|
||||||
|
|
||||||
|
// Open in the new tab
|
||||||
action_link_tab.set_state(&request_var);
|
action_link_tab.set_state(&request_var);
|
||||||
action_link_copy.set_state(&request_var);
|
action_link_copy_text.set_enabled(!request_str.is_empty());
|
||||||
|
|
||||||
|
action_link_copy_url.set_state(&request_var);
|
||||||
|
action_link_copy_text.set_enabled(!request_str.is_empty());
|
||||||
|
|
||||||
|
{
|
||||||
|
// Copy link text
|
||||||
|
let mut start_iter = iter;
|
||||||
|
let mut end_iter = iter;
|
||||||
|
if !start_iter.starts_tag(Some(&tag)) {
|
||||||
|
start_iter.backward_to_tag_toggle(Some(&tag));
|
||||||
|
}
|
||||||
|
if !end_iter.ends_tag(Some(&tag)) {
|
||||||
|
end_iter.forward_to_tag_toggle(Some(&tag));
|
||||||
|
}
|
||||||
|
let tagged_text = text_view
|
||||||
|
.buffer()
|
||||||
|
.text(&start_iter, &end_iter, false)
|
||||||
|
.replace(LINK_EXTERNAL_INDICATOR, "")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
action_link_copy_text.set_state(&tagged_text.to_variant());
|
||||||
|
action_link_copy_text.set_enabled(!tagged_text.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy link text (if) selected
|
||||||
|
if let Some((sel_start, sel_end)) = buffer.selection_bounds() {
|
||||||
|
let selected_tag_text = buffer.text(&sel_start, &sel_end, false);
|
||||||
|
action_link_copy_text_selected
|
||||||
|
.set_state(&selected_tag_text.to_variant());
|
||||||
|
action_link_copy_text_selected
|
||||||
|
.set_enabled(!selected_tag_text.is_empty());
|
||||||
|
} else {
|
||||||
|
action_link_copy_text_selected.set_enabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bookmark
|
||||||
|
action_link_bookmark.set_state(&request_var);
|
||||||
|
action_link_bookmark.set_enabled(is_prefixable_link(&request_str));
|
||||||
|
|
||||||
|
// Download (new tab)
|
||||||
action_link_download.set_state(&request_var);
|
action_link_download.set_state(&request_var);
|
||||||
action_link_download.set_enabled(is_prefixable_link(&request_str));
|
action_link_download.set_enabled(is_prefixable_link(&request_str));
|
||||||
|
|
||||||
|
// View as Source (new tab)
|
||||||
action_link_source.set_state(&request_var);
|
action_link_source.set_state(&request_var);
|
||||||
action_link_source.set_enabled(is_prefixable_link(&request_str));
|
action_link_source.set_enabled(is_prefixable_link(&request_str));
|
||||||
|
|
||||||
|
// Toggle
|
||||||
link_context
|
link_context
|
||||||
.set_pointing_to(Some(>k::gdk::Rectangle::new(x, y, 1, 1)));
|
.set_pointing_to(Some(>k::gdk::Rectangle::new(x, y, 1, 1)));
|
||||||
link_context.popup();
|
link_context.popup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -580,5 +679,6 @@ fn link_prefix(request: String, prefix: &str) -> String {
|
||||||
format!("{prefix}{}", request.trim_start_matches(prefix))
|
format!("{prefix}{}", request.trim_start_matches(prefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LINK_EXTERNAL_INDICATOR: &str = "⇖";
|
||||||
const LINK_PREFIX_DOWNLOAD: &str = "download:";
|
const LINK_PREFIX_DOWNLOAD: &str = "download:";
|
||||||
const LINK_PREFIX_SOURCE: &str = "source:";
|
const LINK_PREFIX_SOURCE: &str = "source:";
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ mod gutter;
|
||||||
mod tags;
|
mod tags;
|
||||||
|
|
||||||
use super::{ItemAction, WindowAction};
|
use super::{ItemAction, WindowAction};
|
||||||
use crate::app::browser::window::action::Position;
|
use crate::{app::browser::window::action::Position, profile::Profile};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
EventControllerMotion, GestureClick, TextBuffer, TextSearchFlags, TextTag, TextTagTable,
|
EventControllerMotion, GestureClick, TextBuffer, TextSearchFlags, TextTag, TextTagTable,
|
||||||
TextView, TextWindowType, UriLauncher, Window, WrapMode,
|
TextView, TextWindowType, UriLauncher, Window, WrapMode,
|
||||||
gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY, BUTTON_SECONDARY, RGBA},
|
gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY, BUTTON_SECONDARY, Display, RGBA},
|
||||||
gio::{Cancellable, SimpleAction, SimpleActionGroup},
|
gio::{Cancellable, Menu, SimpleAction, SimpleActionGroup},
|
||||||
glib::{ControlFlow, GString, Uri, idle_add_local, uri_unescape_string, uuid_string_random},
|
glib::{ControlFlow, GString, Uri, idle_add_local, uri_unescape_string, uuid_string_random},
|
||||||
prelude::{PopoverExt, TextBufferExt, TextTagExt, TextViewExt, WidgetExt},
|
prelude::{PopoverExt, TextBufferExt, TextTagExt, TextViewExt, WidgetExt},
|
||||||
};
|
};
|
||||||
|
|
@ -27,6 +27,7 @@ impl Markdown {
|
||||||
/// Build new `Self`
|
/// Build new `Self`
|
||||||
pub fn build(
|
pub fn build(
|
||||||
(window_action, item_action): (&Rc<WindowAction>, &Rc<ItemAction>),
|
(window_action, item_action): (&Rc<WindowAction>, &Rc<ItemAction>),
|
||||||
|
profile: &Rc<Profile>,
|
||||||
base: &Uri,
|
base: &Uri,
|
||||||
markdown: &str,
|
markdown: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -88,14 +89,39 @@ impl Markdown {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let action_link_copy =
|
let action_link_copy_url =
|
||||||
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
action_link_copy.connect_activate(|this, _| {
|
action_link_copy_url.connect_activate(|this, _| {
|
||||||
gtk::gdk::Display::default()
|
Display::default()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clipboard()
|
.clipboard()
|
||||||
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
||||||
});
|
});
|
||||||
|
let action_link_copy_text =
|
||||||
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
|
action_link_copy_text.connect_activate(|this, _| {
|
||||||
|
Display::default()
|
||||||
|
.unwrap()
|
||||||
|
.clipboard()
|
||||||
|
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
||||||
|
});
|
||||||
|
let action_link_copy_text_selected =
|
||||||
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
|
action_link_copy_text_selected.connect_activate(|this, _| {
|
||||||
|
Display::default()
|
||||||
|
.unwrap()
|
||||||
|
.clipboard()
|
||||||
|
.set_text(&this.state().unwrap().get::<String>().unwrap())
|
||||||
|
});
|
||||||
|
let action_link_bookmark =
|
||||||
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
|
action_link_bookmark.connect_activate({
|
||||||
|
let p = profile.clone();
|
||||||
|
move |this, _| {
|
||||||
|
let state = this.state().unwrap().get::<String>().unwrap();
|
||||||
|
p.bookmark.toggle(&state, None).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
let action_link_download =
|
let action_link_download =
|
||||||
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
SimpleAction::new_stateful(&uuid_string_random(), None, &String::new().to_variant());
|
||||||
action_link_download.connect_activate({
|
action_link_download.connect_activate({
|
||||||
|
|
@ -130,14 +156,17 @@ impl Markdown {
|
||||||
Some(&{
|
Some(&{
|
||||||
let g = SimpleActionGroup::new();
|
let g = SimpleActionGroup::new();
|
||||||
g.add_action(&action_link_tab);
|
g.add_action(&action_link_tab);
|
||||||
g.add_action(&action_link_copy);
|
g.add_action(&action_link_copy_url);
|
||||||
|
g.add_action(&action_link_copy_text);
|
||||||
|
g.add_action(&action_link_copy_text_selected);
|
||||||
|
g.add_action(&action_link_bookmark);
|
||||||
g.add_action(&action_link_download);
|
g.add_action(&action_link_download);
|
||||||
g.add_action(&action_link_source);
|
g.add_action(&action_link_source);
|
||||||
g
|
g
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let link_context = gtk::PopoverMenu::from_model(Some(&{
|
let link_context = gtk::PopoverMenu::from_model(Some(&{
|
||||||
let m = gtk::gio::Menu::new();
|
let m = Menu::new();
|
||||||
m.append(
|
m.append(
|
||||||
Some("Open Link in New Tab"),
|
Some("Open Link in New Tab"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
|
|
@ -145,27 +174,56 @@ impl Markdown {
|
||||||
action_link_tab.name()
|
action_link_tab.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
m.append(
|
m.append_section(None, &{
|
||||||
Some("Copy Link"),
|
let m_copy = Menu::new();
|
||||||
|
m_copy.append(
|
||||||
|
Some("Copy Link URL"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"{link_context_group_id}.{}",
|
"{link_context_group_id}.{}",
|
||||||
action_link_copy.name()
|
action_link_copy_url.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
m.append(
|
m_copy.append(
|
||||||
|
Some("Copy Link Text"),
|
||||||
|
Some(&format!(
|
||||||
|
"{link_context_group_id}.{}",
|
||||||
|
action_link_copy_text.name()
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
m_copy.append(
|
||||||
|
Some("Copy Link Text Selected"),
|
||||||
|
Some(&format!(
|
||||||
|
"{link_context_group_id}.{}",
|
||||||
|
action_link_copy_text_selected.name()
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
m_copy
|
||||||
|
});
|
||||||
|
m.append_section(None, &{
|
||||||
|
let m_other = Menu::new();
|
||||||
|
m_other.append(
|
||||||
|
Some("Bookmark Link"), // @TODO highlight state
|
||||||
|
Some(&format!(
|
||||||
|
"{link_context_group_id}.{}",
|
||||||
|
action_link_bookmark.name()
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
m_other.append(
|
||||||
Some("Download Link"),
|
Some("Download Link"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"{link_context_group_id}.{}",
|
"{link_context_group_id}.{}",
|
||||||
action_link_download.name()
|
action_link_download.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
m.append(
|
m_other.append(
|
||||||
Some("View Link as Source"),
|
Some("View Link as Source"),
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"{link_context_group_id}.{}",
|
"{link_context_group_id}.{}",
|
||||||
action_link_source.name()
|
action_link_source.name()
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
m_other
|
||||||
|
});
|
||||||
m
|
m
|
||||||
}));
|
}));
|
||||||
link_context.set_parent(&text_view);
|
link_context.set_parent(&text_view);
|
||||||
|
|
@ -231,18 +289,61 @@ impl Markdown {
|
||||||
let request_str = uri.to_str();
|
let request_str = uri.to_str();
|
||||||
let request_var = request_str.to_variant();
|
let request_var = request_str.to_variant();
|
||||||
|
|
||||||
|
// Open in the new tab
|
||||||
action_link_tab.set_state(&request_var);
|
action_link_tab.set_state(&request_var);
|
||||||
action_link_copy.set_state(&request_var);
|
action_link_copy_text.set_enabled(!request_str.is_empty());
|
||||||
|
|
||||||
|
action_link_copy_url.set_state(&request_var);
|
||||||
|
action_link_copy_text.set_enabled(!request_str.is_empty());
|
||||||
|
|
||||||
|
{
|
||||||
|
// Copy link text
|
||||||
|
let mut start_iter = iter;
|
||||||
|
let mut end_iter = iter;
|
||||||
|
if !start_iter.starts_tag(Some(&tag)) {
|
||||||
|
start_iter.backward_to_tag_toggle(Some(&tag));
|
||||||
|
}
|
||||||
|
if !end_iter.ends_tag(Some(&tag)) {
|
||||||
|
end_iter.forward_to_tag_toggle(Some(&tag));
|
||||||
|
}
|
||||||
|
let tagged_text = text_view
|
||||||
|
.buffer()
|
||||||
|
.text(&start_iter, &end_iter, false)
|
||||||
|
.replace(LINK_EXTERNAL_INDICATOR, "")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
action_link_copy_text.set_state(&tagged_text.to_variant());
|
||||||
|
action_link_copy_text.set_enabled(!tagged_text.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy link text (if) selected
|
||||||
|
if let Some((sel_start, sel_end)) = buffer.selection_bounds() {
|
||||||
|
let selected_tag_text = buffer.text(&sel_start, &sel_end, false);
|
||||||
|
action_link_copy_text_selected
|
||||||
|
.set_state(&selected_tag_text.to_variant());
|
||||||
|
action_link_copy_text_selected
|
||||||
|
.set_enabled(!selected_tag_text.is_empty());
|
||||||
|
} else {
|
||||||
|
action_link_copy_text_selected.set_enabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bookmark
|
||||||
|
action_link_bookmark.set_state(&request_var);
|
||||||
|
action_link_bookmark.set_enabled(is_prefixable_link(&request_str));
|
||||||
|
|
||||||
|
// Download (new tab)
|
||||||
action_link_download.set_state(&request_var);
|
action_link_download.set_state(&request_var);
|
||||||
action_link_download.set_enabled(is_prefixable_link(&request_str));
|
action_link_download.set_enabled(is_prefixable_link(&request_str));
|
||||||
|
|
||||||
|
// View as Source (new tab)
|
||||||
action_link_source.set_state(&request_var);
|
action_link_source.set_state(&request_var);
|
||||||
action_link_source.set_enabled(is_prefixable_link(&request_str));
|
action_link_source.set_enabled(is_prefixable_link(&request_str));
|
||||||
|
|
||||||
|
// Toggle
|
||||||
link_context
|
link_context
|
||||||
.set_pointing_to(Some(>k::gdk::Rectangle::new(x, y, 1, 1)));
|
.set_pointing_to(Some(>k::gdk::Rectangle::new(x, y, 1, 1)));
|
||||||
link_context.popup();
|
link_context.popup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -405,5 +506,6 @@ fn link_prefix(request: String, prefix: &str) -> String {
|
||||||
format!("{prefix}{}", request.trim_start_matches(prefix))
|
format!("{prefix}{}", request.trim_start_matches(prefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LINK_EXTERNAL_INDICATOR: &str = "⇖";
|
||||||
const LINK_PREFIX_DOWNLOAD: &str = "download:";
|
const LINK_PREFIX_DOWNLOAD: &str = "download:";
|
||||||
const LINK_PREFIX_SOURCE: &str = "source:";
|
const LINK_PREFIX_SOURCE: &str = "source:";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue