1919##############################################################################
2020# MarkdownIt imports.
2121from markdown_it import MarkdownIt
22+ from markdown_it .token import Token
2223from mdit_py_plugins import front_matter
2324
2425##############################################################################
2526# Textual imports.
2627from textual import on , work
2728from textual .app import ComposeResult
29+ from textual .await_complete import AwaitComplete
2830from textual .containers import Vertical
2931from textual .events import Click
3032from textual .message import Message
3133from textual .reactive import var
32- from textual .widgets import Label , Markdown , Rule
34+ from textual .widgets import Collapsible , Label , Markdown , Rule
35+ from textual .widgets .markdown import MarkdownBlock
3336
3437##############################################################################
3538# Textual enhanced imports.
@@ -108,6 +111,94 @@ class MarkdownScroll(EnhancedVerticalScroll):
108111 """
109112
110113
114+ ##############################################################################
115+ class FrontMatter (Collapsible ):
116+ """A widget to show the front matter of Markdown document."""
117+
118+ DEFAULT_CSS = """
119+ FrontMatter {
120+ padding: 0;
121+ border-top: none;
122+ display: none;
123+ background: transparent;
124+
125+ &.--exists {
126+ display: block;
127+ }
128+
129+ &.-collapsed {
130+ margin-bottom: 1;
131+ }
132+
133+ CollapsibleTitle {
134+ padding: 0;
135+ color: $accent;
136+ }
137+
138+ Contents {
139+ padding: 0;
140+ Label {
141+ padding: 0 0 0 2;
142+ }
143+ }
144+ }
145+ """
146+
147+ front_matter : var [str | None ] = var (None )
148+ """The front matter to show."""
149+
150+ def __init__ (self ) -> None :
151+ super ().__init__ (Label (), Rule (), title = "Front matter" )
152+
153+ def _watch_front_matter (self ) -> None :
154+ self .set_class (
155+ bool (self .front_matter ) and load_configuration ().show_front_matter ,
156+ "--exists" ,
157+ )
158+ self .query_one (Label ).update (self .front_matter or "" )
159+
160+
161+ ##############################################################################
162+ class HikeDown (Markdown ):
163+ """Hike's `Markdown` wrapper widget."""
164+
165+ DEFAULT_CSS = """
166+ HikeDown {
167+ background: transparent;
168+ }
169+ """
170+
171+ front_matter : var [str | None ] = var (None )
172+ """The content of any front matter found in the Markdown file."""
173+
174+ def __init__ (self ) -> None :
175+ """Initialise the widget."""
176+ super ().__init__ (
177+ open_links = False ,
178+ parser_factory = lambda : MarkdownIt ("gfm-like" ).use (
179+ front_matter .front_matter_plugin
180+ ),
181+ )
182+
183+ def update (self , markdown : str ) -> AwaitComplete :
184+ self .front_matter = None
185+ return super ().update (markdown )
186+
187+ def unhandled_token (self , token : Token ) -> MarkdownBlock | None :
188+ """Handle tokens that Textual's Markdown didn't.
189+
190+ Args:
191+ token: The token to handle.
192+
193+ Returns:
194+ `None` or a `MarkdownBlock`.
195+ """
196+ if token .type == "front_matter" :
197+ self .front_matter = token .content
198+ return None
199+ return super ().unhandled_token (token )
200+
201+
111202##############################################################################
112203class Viewer (Vertical , can_focus = False ):
113204 """The Markdown viewer widget."""
@@ -121,9 +212,6 @@ class Viewer(Vertical, can_focus=False):
121212 EnhancedVerticalScroll {
122213 background: transparent;
123214 }
124- Markdown {
125- background: transparent;
126- }
127215 Rule {
128216 height: 1;
129217 margin: 0 !important;
@@ -165,13 +253,9 @@ def compose(self) -> ComposeResult:
165253 """Compose the content of the viewer."""
166254 yield ViewerTitle ()
167255 yield Rule (line_style = "heavy" )
256+ yield FrontMatter ()
168257 with MarkdownScroll (id = "document" ):
169- yield Markdown (
170- open_links = False ,
171- parser_factory = lambda : MarkdownIt ("gfm-like" ).use (
172- front_matter .front_matter_plugin
173- ),
174- )
258+ yield HikeDown ()
175259
176260 def focus (self , scroll_visible : bool = True ) -> Self :
177261 """Focus the viewer.
@@ -360,9 +444,13 @@ async def _update_markdown(self, message: Loaded) -> None:
360444 Args:
361445 message: The message requesting the update.
362446 """
447+ front_matter_had_focus = self .query_one (FrontMatter ).has_focus_within
363448 self .query_one (ViewerTitle ).location = self .location
364449 self ._source = message .markdown
365- await self .query_one (Markdown ).update (message .markdown )
450+ await (hikedown := self .query_one (HikeDown )).update (message .markdown )
451+ if front_matter_had_focus and not hikedown .front_matter :
452+ self .query_one (MarkdownScroll ).focus ()
453+ self .query_one (FrontMatter ).front_matter = hikedown .front_matter
366454 if (
367455 message .remember
368456 and self .location
0 commit comments