@@ -68,8 +68,69 @@ def format_plain_text(text: str) -> str:
6868 return "" .join (segments )
6969
7070
71+ def process_italic (text : str ) -> str :
72+ """Wrap *text* in <em> tags."""
73+ # Split by *
74+ parts_star = text .split ("*" )
75+ processed_parts : list [str ] = []
76+
77+ for i , part in enumerate (parts_star ):
78+ if i % 2 == 1 :
79+ # Odd segments inside *
80+ processed_parts .append (f"<em>{ html .escape (part )} </em>" )
81+ else :
82+ # Even segments outside *, handle _ next
83+ sub_parts = part .split ("_" )
84+ for j , sub in enumerate (sub_parts ):
85+ if j % 2 == 1 :
86+ sub_parts [j ] = f"<em>{ html .escape (sub )} </em>"
87+ else :
88+ sub_parts [j ] = html .escape (sub )
89+ processed_parts .append ("" .join (sub_parts ))
90+
91+ return "" .join (processed_parts )
92+
93+
94+ def process_bold (text : str ) -> str :
95+ """Wrap **text** or __text__ in <strong> tags."""
96+ # Split by ** first
97+ parts = text .split ("**" )
98+ processed_parts : list [str ] = []
99+
100+ for i , part in enumerate (parts ):
101+ if i % 2 == 1 :
102+ # Odd segments inside **
103+ # Recurse for italics inside bold
104+ processed_parts .append (f"<strong>{ process_italic (part )} </strong>" )
105+ else :
106+ # Even segments outside **, handle __ next
107+ # Note: __ splits
108+ sub_parts = part .split ("__" )
109+ for j , sub in enumerate (sub_parts ):
110+ if j % 2 == 1 :
111+ sub_parts [j ] = f"<strong>{ process_italic (sub )} </strong>"
112+ else :
113+ sub_parts [j ] = process_italic (sub )
114+ processed_parts .append ("" .join (sub_parts ))
115+
116+ return "" .join (processed_parts )
117+
118+
119+ def process_inline_markdown (text : str ) -> str :
120+ """Escape text for HTML and wrap inline markdown (code, bold, italic)."""
121+ parts = text .split ("`" )
122+ for i , part in enumerate (parts ):
123+ if i % 2 == 1 :
124+ # Odd segments are inside backticks: escaping only
125+ parts [i ] = f"<code>{ html .escape (part )} </code>"
126+ else :
127+ # Even segments are outside backticks: process bold/italic
128+ parts [i ] = process_bold (part )
129+ return "" .join (parts )
130+
131+
71132def format_html_text (text : str | None ) -> str :
72- """Render Markdown-style links as HTML anchors while escaping content ."""
133+ """Render Markdown-style links and inline formatting as HTML ."""
73134
74135 if text is None :
75136 return ""
@@ -79,21 +140,26 @@ def format_html_text(text: str | None) -> str:
79140 parts : list [str ] = []
80141 cursor = 0
81142 for start , end , label , url in iter_markdown_links (text ):
82- parts .append (html .escape (text [cursor :start ]))
143+ # Process text before the link
144+ parts .append (process_inline_markdown (text [cursor :start ]))
145+
83146 clean_url = url .strip ()
84- escaped_label = html .escape (label )
147+ # Process markdown inside the link label
148+ processed_label = process_inline_markdown (label )
149+
85150 if clean_url :
86151 escaped_url = html .escape (clean_url , quote = True )
87152 anchor = (
88153 f"<a href=\" { escaped_url } \" "
89- f"rel=\" noopener noreferrer\" >{ escaped_label } </a>"
154+ f"rel=\" noopener noreferrer\" >{ processed_label } </a>"
90155 )
91156 parts .append (anchor )
92157 else :
93- parts .append (escaped_label )
158+ parts .append (processed_label )
94159 cursor = end
95160
96- parts .append (html .escape (text [cursor :]))
161+ # Process remaining text
162+ parts .append (process_inline_markdown (text [cursor :]))
97163 return "" .join (parts )
98164
99165
0 commit comments