1+ import { Marked } from "../../lib/mjs/marked.min.mjs"
2+ import hljs from "../../lib/mjs/highlight.min.mjs"
3+
4+ export const marked = ( ( ) => {
5+ const ret = new Marked (
6+ markedHighlight ( {
7+ langPrefix : 'hljs language-' ,
8+ highlight ( code , lang , info ) {
9+ const language = hljs . getLanguage ( lang ) ? lang : 'plaintext'
10+ return hljs . highlight ( code , { language } ) . value
11+ }
12+ } )
13+ )
14+ ret . use ( { extensions :[ thinkTag ( ) ] } )
15+ //ret.use({ extensions: [divExtension()] })
16+ return ret
17+ } ) ( ) ;
18+
19+ export function renderMarkdown ( content ) {
20+ if ( content ) {
21+ content = content
22+ . replaceAll ( `\\[ \\boxed{` , '\n<span class="inline-block text-xl text-blue-500 bg-blue-50 px-3 py-1 rounded">' )
23+ . replaceAll ( '} \\]' , '</span>\n' )
24+ }
25+ return marked . parse ( content )
26+ }
27+
28+ // export async function renderMarkdown(body) {
29+ // const rawHtml = marked.parse(body)
30+ // return <main dangerouslySetInnerHTML={{ __html: rawHtml }} />
31+ // }
32+
33+ export function markedHighlight ( options ) {
34+ if ( typeof options === 'function' ) {
35+ options = {
36+ highlight : options
37+ }
38+ }
39+
40+ if ( ! options || typeof options . highlight !== 'function' ) {
41+ throw new Error ( 'Must provide highlight function' )
42+ }
43+
44+ if ( typeof options . langPrefix !== 'string' ) {
45+ options . langPrefix = 'language-'
46+ }
47+
48+ return {
49+ async : ! ! options . async ,
50+ walkTokens ( token ) {
51+ if ( token . type !== 'code' ) {
52+ return
53+ }
54+
55+ const lang = getLang ( token . lang )
56+
57+ if ( options . async ) {
58+ return Promise . resolve ( options . highlight ( token . text , lang , token . lang || '' ) ) . then ( updateToken ( token ) )
59+ }
60+
61+ const code = options . highlight ( token . text , lang , token . lang || '' )
62+ if ( code instanceof Promise ) {
63+ throw new Error ( 'markedHighlight is not set to async but the highlight function is async. Set the async option to true on markedHighlight to await the async highlight function.' )
64+ }
65+ updateToken ( token ) ( code )
66+ } ,
67+ renderer : {
68+ code ( code , infoString ) {
69+ const lang = getLang ( infoString )
70+ let text = code . text
71+ const classAttr = lang
72+ ? ` class="${ options . langPrefix } ${ escape ( lang ) } "`
73+ : ' class="hljs"' ;
74+ text = text . replace ( / \n $ / , '' )
75+ return `<pre><code${ classAttr } >${ code . escaped ? text : escape ( text , true ) } \n</code></pre>`
76+ }
77+ }
78+ }
79+ }
80+
81+ function getLang ( lang ) {
82+ return ( lang || '' ) . match ( / \S * / ) [ 0 ]
83+ }
84+
85+ function updateToken ( token ) {
86+ return code => {
87+ if ( typeof code === 'string' && code !== token . text ) {
88+ token . escaped = true
89+ token . text = code
90+ }
91+ }
92+ }
93+
94+ // copied from marked helpers
95+ const escapeTest = / [ & < > " ' ] /
96+ const escapeReplace = new RegExp ( escapeTest . source , 'g' )
97+ const escapeTestNoEncode = / [ < > " ' ] | & (? ! ( # \d { 1 , 7 } | # [ X x ] [ a - f A - F 0 - 9 ] { 1 , 6 } | \w + ) ; ) /
98+ const escapeReplaceNoEncode = new RegExp ( escapeTestNoEncode . source , 'g' )
99+ const escapeReplacements = {
100+ '&' : '&' ,
101+ '<' : '<' ,
102+ '>' : '>' ,
103+ '"' : '"' ,
104+ "'" : '''
105+ }
106+ const getEscapeReplacement = ch => escapeReplacements [ ch ]
107+ function escape ( html , encode ) {
108+ if ( encode ) {
109+ if ( escapeTest . test ( html ) ) {
110+ return html . replace ( escapeReplace , getEscapeReplacement )
111+ }
112+ } else {
113+ if ( escapeTestNoEncode . test ( html ) ) {
114+ return html . replace ( escapeReplaceNoEncode , getEscapeReplacement )
115+ }
116+ }
117+
118+ return html
119+ }
120+
121+ /**
122+ * Marked.js extension for rendering <think> tags as expandable, scrollable components
123+ * using Tailwind CSS
124+ */
125+
126+ // Extension for Marked.js to handle <think> tags
127+ function thinkTag ( ) {
128+ globalThis . toggleThink = toggleThink
129+ return ( {
130+ name : 'thinkTag' ,
131+ level : 'block' ,
132+ start ( src ) {
133+ return src . match ( / ^ < t h i n k > / ) ?. index ;
134+ } ,
135+ tokenizer ( src ) {
136+ const rule = / ^ < t h i n k > ( [ \s \S ] * ?) < \/ t h i n k > /
137+ const match = rule . exec ( src )
138+ if ( match ) {
139+ return {
140+ type : 'thinkTag' ,
141+ raw : match [ 0 ] ,
142+ content : match [ 1 ] . trim ( ) ,
143+ }
144+ }
145+ return undefined
146+ } ,
147+ renderer ( token ) {
148+ // Parse the markdown content inside the think tag
149+ const parsedContent = marked . parse ( token . content )
150+
151+ // Generate a unique ID for this think component
152+ const uniqueId = 'think-' + Math . random ( ) . toString ( 36 ) . substring ( 2 , 10 )
153+
154+ // Create the expandable, scrollable component with Tailwind CSS
155+ return `
156+ <div class="my-4 border border-gray-200 rounded-lg shadow-sm">
157+ <button
158+ id="${ uniqueId } -toggle"
159+ class="flex justify-between items-center w-full py-2 px-4 text-left text-gray-700 font-medium hover:bg-gray-50 focus:outline-none"
160+ onclick="toggleThink('${ uniqueId } ')">
161+ <span>Thinking</span>
162+ <svg id="${ uniqueId } -icon" class="h-5 w-5 text-gray-500 transform transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
163+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
164+ </svg>
165+ </button>
166+ <div
167+ id="${ uniqueId } -content"
168+ class="hidden overflow-auto max-h-64 px-4 border-t border-gray-200 bg-gray-50"
169+ style="max-height:16rem;">
170+ ${ parsedContent }
171+ </div>
172+ </div>
173+ `
174+ }
175+ } )
176+ }
177+
178+ // JavaScript function to toggle the visibility of the think content
179+ function toggleThink ( id ) {
180+ const content = document . getElementById ( `${ id } -content` )
181+ const icon = document . getElementById ( `${ id } -icon` )
182+
183+ if ( content . classList . contains ( 'hidden' ) ) {
184+ content . classList . remove ( 'hidden' )
185+ icon . classList . add ( 'rotate-180' )
186+ } else {
187+ content . classList . add ( 'hidden' )
188+ icon . classList . remove ( 'rotate-180' )
189+ }
190+ }
0 commit comments