@@ -24,6 +24,10 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
2424 Scratch . HeaderBar ? toolbar = null ;
2525 Gtk . ToggleButton ? preview_button = null ;
2626
27+ // Signal handler IDs
28+ ulong hook_toolbar_id = 0 ;
29+ ulong hook_document_id = 0 ;
30+
2731 // Store preview state per document
2832 private Gee . HashMap<Scratch . Services . Document , PreviewState > preview_states;
2933
@@ -37,6 +41,7 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
3741 public Gtk . Paned ? paned = null ;
3842 public Gtk . ScrolledWindow ? original_scroll = null ;
3943 public bool visible = false ;
44+ public ulong buffer_changed_id = 0 ;
4045
4146 public PreviewState () {
4247 web_view = new WebKit .WebView ();
@@ -73,6 +78,25 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
7378 }
7479 return false ;
7580 });
81+
82+ // Handle navigation requests - Open links in external browser
83+ web_view. decide_policy. connect ((decision, type) = > {
84+ if (type == WebKit . PolicyDecisionType . NAVIGATION_ACTION ) {
85+ var nav_decision = (WebKit . NavigationPolicyDecision ) decision;
86+ var action = nav_decision. navigation_action;
87+
88+ if (action. get_navigation_type () == WebKit . NavigationType . LINK_CLICKED ) {
89+ try {
90+ Gtk . show_uri (null , action. get_request (). uri, Gdk . CURRENT_TIME );
91+ } catch (Error e) {
92+ warning (" Failed to open URI: %s " , e. message);
93+ }
94+ decision. ignore ();
95+ return true ;
96+ }
97+ }
98+ return false ;
99+ });
76100 }
77101 }
78102
@@ -88,12 +112,12 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
88112 preview_button. no_show_all = true ;
89113
90114 // Hook into toolbar
91- plugins. hook_toolbar. connect ((t) = > {
115+ hook_toolbar_id = plugins. hook_toolbar. connect ((t) = > {
92116 toolbar = t;
93117 });
94118
95119 // Hook into document changes
96- plugins. hook_document. connect ((doc) = > {
120+ hook_document_id = plugins. hook_document. connect ((doc) = > {
97121 if (current_source != null ) {
98122 current_source. notify[" language" ]. disconnect (on_language_changed);
99123 }
@@ -191,6 +215,10 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
191215 warning (" Could not find parent of scroll window" );
192216 return ;
193217 }
218+
219+ // Capture width BEFORE removing from parent
220+ int width = scroll. get_allocated_width ();
221+ if (width <= 0 ) width = 600 ; // Fallback width
194222
195223 // Store reference to original scroll window
196224 state. original_scroll = scroll;
@@ -206,7 +234,7 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
206234 state. paned. pack2 (state. scrolled_window, true , false );
207235
208236 // Set initial position to 50/50 split
209- state. paned. position = scroll . get_allocated_width () / 2 ;
237+ state. paned. position = width / 2 ;
210238
211239 // Add the paned widget back to the parent
212240 if (scroll_parent is Gtk . Grid ) {
@@ -222,9 +250,11 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
222250 update_preview (doc, state);
223251
224252 // Connect to buffer changes for live updates
225- doc. source_view. buffer. changed. connect (() = > {
226- on_text_changed (doc);
227- });
253+ if (state. buffer_changed_id == 0 ) {
254+ state. buffer_changed_id = doc. source_view. buffer. changed. connect (() = > {
255+ on_text_changed (doc);
256+ });
257+ }
228258 }
229259
230260 private void hide_preview (Scratch .Services .Document doc ) {
@@ -246,6 +276,10 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
246276 // Remove the scroll window from the paned
247277 state. paned. remove (state. original_scroll);
248278
279+ // CRITICAL: Remove the preview scrolled window too, otherwise it gets destroyed
280+ // when state.paned is removed/destroyed.
281+ state. paned. remove (state. scrolled_window);
282+
249283 // Remove the paned from its parent
250284 paned_parent. remove (state. paned);
251285
@@ -256,6 +290,12 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
256290 ((Gtk . Box ) paned_parent). pack_start (state. original_scroll, true , true , 0 );
257291 }
258292
293+ // Disconnect signal
294+ if (state. buffer_changed_id > 0 ) {
295+ doc. source_view. buffer. disconnect (state. buffer_changed_id);
296+ state. buffer_changed_id = 0 ;
297+ }
298+
259299 state. visible = false ;
260300 state. paned = null ;
261301 }
@@ -290,7 +330,21 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
290330 var markdown_text = buffer. get_text (start, end, false );
291331
292332 var html = markdown_to_html (markdown_text);
293- state. web_view. load_html (html, null );
333+
334+ // Fix relative paths by providing a base URI
335+ string ? base_uri = null ;
336+ if (doc. file != null ) {
337+ var parent = doc. file. get_parent ();
338+ if (parent != null ) {
339+ base_uri = parent. get_uri ();
340+ // Ensure URI ends with a slash so relative files resolve correctly
341+ if (! base_uri. has_suffix (" /" )) {
342+ base_uri + = " /" ;
343+ }
344+ }
345+ }
346+
347+ state. web_view. load_html (html, base_uri);
294348 }
295349
296350 private string markdown_to_html (string markdown ) {
@@ -550,13 +604,13 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
550604 var code_regex = new Regex (" `(.+?)`" );
551605 result = code_regex. replace (result, - 1 , 0 , " <code>\\ 1</code>" );
552606
607+ // Images  - MUST BE BEFORE LINKS
608+ var img_regex = new Regex (" !\\ [(.+?)\\ ]\\ ((.+?)\\ )" );
609+ result = img_regex. replace (result, - 1 , 0 , " <img src=\"\\ 2\" alt=\"\\ 1\" >" );
610+
553611 // Links [text](url)
554612 var link_regex = new Regex (" \\ [(.+?)\\ ]\\ ((.+?)\\ )" );
555613 result = link_regex. replace (result, - 1 , 0 , " <a href=\"\\ 2\" >\\ 1</a>" );
556-
557- // Images 
558- var img_regex = new Regex (" !\\ [(.+?)\\ ]\\ ((.+?)\\ )" );
559- result = img_regex. replace (result, - 1 , 0 , " <img src=\"\\ 2\" alt=\"\\ 1\" >" );
560614 } catch (RegexError e) {
561615 warning (" Regex error: %s " , e. message);
562616 }
@@ -573,6 +627,24 @@ public class Code.Plugins.MarkdownPreview : Peas.ExtensionBase, Scratch.Services
573627 }
574628 preview_states. clear ();
575629
630+ // Disconnect main signal handlers
631+ if (plugins != null ) {
632+ if (hook_toolbar_id > 0 ) {
633+ plugins. disconnect (hook_toolbar_id);
634+ hook_toolbar_id = 0 ;
635+ }
636+ if (hook_document_id > 0 ) {
637+ plugins. disconnect (hook_document_id);
638+ hook_document_id = 0 ;
639+ }
640+ }
641+
642+ // Disconnect from current source
643+ if (current_source != null ) {
644+ current_source. notify[" language" ]. disconnect (on_language_changed);
645+ current_source = null ;
646+ }
647+
576648 if (toolbar != null && preview_button != null && preview_button. parent != null ) {
577649 toolbar. remove (preview_button);
578650 }
0 commit comments