@@ -6,13 +6,13 @@ mod syntax;
66mod tag;
77
88use super :: { ItemAction , WindowAction } ;
9- use crate :: app:: browser:: window:: action:: Position ;
9+ use crate :: { app:: browser:: window:: action:: Position , profile :: Profile } ;
1010pub use error:: Error ;
1111use gtk:: {
1212 EventControllerMotion , GestureClick , TextBuffer , TextTag , TextView , TextWindowType ,
1313 UriLauncher , Window , WrapMode ,
14- gdk:: { BUTTON_MIDDLE , BUTTON_PRIMARY , BUTTON_SECONDARY , RGBA } ,
15- gio:: { Cancellable , SimpleAction , SimpleActionGroup } ,
14+ gdk:: { BUTTON_MIDDLE , BUTTON_PRIMARY , BUTTON_SECONDARY , Display , RGBA } ,
15+ gio:: { Cancellable , Menu , SimpleAction , SimpleActionGroup } ,
1616 glib:: { Uri , uuid_string_random} ,
1717 prelude:: { PopoverExt , TextBufferExt , TextBufferExtManual , TextTagExt , TextViewExt , WidgetExt } ,
1818} ;
@@ -36,6 +36,7 @@ impl Gemini {
3636 /// Build new `Self`
3737 pub fn build (
3838 ( window_action, item_action) : ( & Rc < WindowAction > , & Rc < ItemAction > ) ,
39+ profile : & Rc < Profile > ,
3940 base : & Uri ,
4041 gemtext : & str ,
4142 ) -> Result < Self , Error > {
@@ -220,7 +221,7 @@ impl Gemini {
220221 let mut alt = Vec :: with_capacity ( 2 ) ;
221222
222223 if uri. scheme ( ) != base. scheme ( ) {
223- alt. push ( "⇖" . to_string ( ) ) ;
224+ alt. push ( LINK_EXTERNAL_INDICATOR . to_string ( ) ) ;
224225 }
225226
226227 alt. push ( match link. alt {
@@ -235,9 +236,7 @@ impl Gemini {
235236 . wrap_mode ( WrapMode :: Word )
236237 . build ( ) ;
237238
238- if !tag. text_tag_table . add ( & a) {
239- panic ! ( )
240- }
239+ assert ! ( tag. text_tag_table. add( & a) ) ;
241240
242241 buffer. insert_with_tags ( & mut buffer. end_iter ( ) , & alt. join ( " " ) , & [ & a] ) ;
243242 buffer. insert ( & mut buffer. end_iter ( ) , NEW_LINE ) ;
@@ -296,14 +295,39 @@ impl Gemini {
296295 )
297296 }
298297 } ) ;
299- let action_link_copy =
298+ let action_link_copy_url =
299+ SimpleAction :: new_stateful ( & uuid_string_random ( ) , None , & String :: new ( ) . to_variant ( ) ) ;
300+ action_link_copy_url. connect_activate ( |this, _| {
301+ Display :: default ( )
302+ . unwrap ( )
303+ . clipboard ( )
304+ . set_text ( & this. state ( ) . unwrap ( ) . get :: < String > ( ) . unwrap ( ) )
305+ } ) ;
306+ let action_link_copy_text =
307+ SimpleAction :: new_stateful ( & uuid_string_random ( ) , None , & String :: new ( ) . to_variant ( ) ) ;
308+ action_link_copy_text. connect_activate ( |this, _| {
309+ Display :: default ( )
310+ . unwrap ( )
311+ . clipboard ( )
312+ . set_text ( & this. state ( ) . unwrap ( ) . get :: < String > ( ) . unwrap ( ) )
313+ } ) ;
314+ let action_link_copy_text_selected =
300315 SimpleAction :: new_stateful ( & uuid_string_random ( ) , None , & String :: new ( ) . to_variant ( ) ) ;
301- action_link_copy . connect_activate ( |this, _| {
302- gtk :: gdk :: Display :: default ( )
316+ action_link_copy_text_selected . connect_activate ( |this, _| {
317+ Display :: default ( )
303318 . unwrap ( )
304319 . clipboard ( )
305320 . set_text ( & this. state ( ) . unwrap ( ) . get :: < String > ( ) . unwrap ( ) )
306321 } ) ;
322+ let action_link_bookmark =
323+ SimpleAction :: new_stateful ( & uuid_string_random ( ) , None , & String :: new ( ) . to_variant ( ) ) ;
324+ action_link_bookmark. connect_activate ( {
325+ let p = profile. clone ( ) ;
326+ move |this, _| {
327+ let state = this. state ( ) . unwrap ( ) . get :: < String > ( ) . unwrap ( ) ;
328+ p. bookmark . toggle ( & state, None ) . unwrap ( ) ;
329+ }
330+ } ) ;
307331 let action_link_download =
308332 SimpleAction :: new_stateful ( & uuid_string_random ( ) , None , & String :: new ( ) . to_variant ( ) ) ;
309333 action_link_download. connect_activate ( {
@@ -338,42 +362,74 @@ impl Gemini {
338362 Some ( & {
339363 let g = SimpleActionGroup :: new ( ) ;
340364 g. add_action ( & action_link_tab) ;
341- g. add_action ( & action_link_copy) ;
365+ g. add_action ( & action_link_copy_url) ;
366+ g. add_action ( & action_link_copy_text) ;
367+ g. add_action ( & action_link_copy_text_selected) ;
368+ g. add_action ( & action_link_bookmark) ;
342369 g. add_action ( & action_link_download) ;
343370 g. add_action ( & action_link_source) ;
344371 g
345372 } ) ,
346373 ) ;
347374 let link_context = gtk:: PopoverMenu :: from_model ( Some ( & {
348- let m = gtk :: gio :: Menu :: new ( ) ;
375+ let m = Menu :: new ( ) ;
349376 m. append (
350377 Some ( "Open Link in New Tab" ) ,
351378 Some ( & format ! (
352379 "{link_context_group_id}.{}" ,
353380 action_link_tab. name( )
354381 ) ) ,
355382 ) ;
356- m. append (
357- Some ( "Copy Link" ) ,
358- Some ( & format ! (
359- "{link_context_group_id}.{}" ,
360- action_link_copy. name( )
361- ) ) ,
362- ) ;
363- m. append (
364- Some ( "Download Link" ) ,
365- Some ( & format ! (
366- "{link_context_group_id}.{}" ,
367- action_link_download. name( )
368- ) ) ,
369- ) ;
370- m. append (
371- Some ( "View Link as Source" ) ,
372- Some ( & format ! (
373- "{link_context_group_id}.{}" ,
374- action_link_source. name( )
375- ) ) ,
376- ) ;
383+ m. append_section ( None , & {
384+ let m_copy = Menu :: new ( ) ;
385+ m_copy. append (
386+ Some ( "Copy Link URL" ) ,
387+ Some ( & format ! (
388+ "{link_context_group_id}.{}" ,
389+ action_link_copy_url. name( )
390+ ) ) ,
391+ ) ;
392+ m_copy. append (
393+ Some ( "Copy Link Text" ) ,
394+ Some ( & format ! (
395+ "{link_context_group_id}.{}" ,
396+ action_link_copy_text. name( )
397+ ) ) ,
398+ ) ;
399+ m_copy. append (
400+ Some ( "Copy Link Text Selected" ) ,
401+ Some ( & format ! (
402+ "{link_context_group_id}.{}" ,
403+ action_link_copy_text_selected. name( )
404+ ) ) ,
405+ ) ;
406+ m_copy
407+ } ) ;
408+ m. append_section ( None , & {
409+ let m_other = Menu :: new ( ) ;
410+ m_other. append (
411+ Some ( "Bookmark Link" ) , // @TODO highlight state
412+ Some ( & format ! (
413+ "{link_context_group_id}.{}" ,
414+ action_link_bookmark. name( )
415+ ) ) ,
416+ ) ;
417+ m_other. append (
418+ Some ( "Download Link" ) ,
419+ Some ( & format ! (
420+ "{link_context_group_id}.{}" ,
421+ action_link_download. name( )
422+ ) ) ,
423+ ) ;
424+ m_other. append (
425+ Some ( "View Link as Source" ) ,
426+ Some ( & format ! (
427+ "{link_context_group_id}.{}" ,
428+ action_link_source. name( )
429+ ) ) ,
430+ ) ;
431+ m_other
432+ } ) ;
377433 m
378434 } ) ) ;
379435 link_context. set_parent ( & text_view) ;
@@ -435,18 +491,61 @@ impl Gemini {
435491 let request_str = uri. to_str ( ) ;
436492 let request_var = request_str. to_variant ( ) ;
437493
494+ // Open in the new tab
438495 action_link_tab. set_state ( & request_var) ;
439- action_link_copy. set_state ( & request_var) ;
496+ action_link_copy_text. set_enabled ( !request_str. is_empty ( ) ) ;
497+
498+ action_link_copy_url. set_state ( & request_var) ;
499+ action_link_copy_text. set_enabled ( !request_str. is_empty ( ) ) ;
500+
501+ {
502+ // Copy link text
503+ let mut start_iter = iter;
504+ let mut end_iter = iter;
505+ if !start_iter. starts_tag ( Some ( & tag) ) {
506+ start_iter. backward_to_tag_toggle ( Some ( & tag) ) ;
507+ }
508+ if !end_iter. ends_tag ( Some ( & tag) ) {
509+ end_iter. forward_to_tag_toggle ( Some ( & tag) ) ;
510+ }
511+ let tagged_text = text_view
512+ . buffer ( )
513+ . text ( & start_iter, & end_iter, false )
514+ . replace ( LINK_EXTERNAL_INDICATOR , "" )
515+ . trim ( )
516+ . to_string ( ) ;
517+
518+ action_link_copy_text. set_state ( & tagged_text. to_variant ( ) ) ;
519+ action_link_copy_text. set_enabled ( !tagged_text. is_empty ( ) ) ;
520+ }
521+
522+ // Copy link text (if) selected
523+ if let Some ( ( sel_start, sel_end) ) = buffer. selection_bounds ( ) {
524+ let selected_tag_text = buffer. text ( & sel_start, & sel_end, false ) ;
525+ action_link_copy_text_selected
526+ . set_state ( & selected_tag_text. to_variant ( ) ) ;
527+ action_link_copy_text_selected
528+ . set_enabled ( !selected_tag_text. is_empty ( ) ) ;
529+ } else {
530+ action_link_copy_text_selected. set_enabled ( false ) ;
531+ }
532+
533+ // Bookmark
534+ action_link_bookmark. set_state ( & request_var) ;
535+ action_link_bookmark. set_enabled ( is_prefixable_link ( & request_str) ) ;
440536
537+ // Download (new tab)
441538 action_link_download. set_state ( & request_var) ;
442539 action_link_download. set_enabled ( is_prefixable_link ( & request_str) ) ;
443540
541+ // View as Source (new tab)
444542 action_link_source. set_state ( & request_var) ;
445543 action_link_source. set_enabled ( is_prefixable_link ( & request_str) ) ;
446544
545+ // Toggle
447546 link_context
448547 . set_pointing_to ( Some ( & gtk:: gdk:: Rectangle :: new ( x, y, 1 , 1 ) ) ) ;
449- link_context. popup ( ) ;
548+ link_context. popup ( )
450549 }
451550 }
452551 }
@@ -580,5 +679,6 @@ fn link_prefix(request: String, prefix: &str) -> String {
580679 format ! ( "{prefix}{}" , request. trim_start_matches( prefix) )
581680}
582681
682+ const LINK_EXTERNAL_INDICATOR : & str = "⇖" ;
583683const LINK_PREFIX_DOWNLOAD : & str = "download:" ;
584684const LINK_PREFIX_SOURCE : & str = "source:" ;
0 commit comments