1
+ // --------------- This script is injected into the LeetCode problem page. ------------------
2
+
3
+ // isUIRunning helps to only inject UI once.
4
+ let isUIRunning = false ;
5
+ let panel ;
6
+
7
+ function main ( ) {
8
+ if ( isUIRunning ) return ;
9
+
10
+ // find area to inject button
11
+ const targetArea = document . querySelector ( 'div.flex.items-center.gap-4' ) ;
12
+ if ( targetArea ) {
13
+ const analyzeBtn = document . createElement ( 'button' ) ;
14
+ analyzeBtn . textContent = 'Analyze Problem' ;
15
+ analyzeBtn . id = 'analyze-btn' ;
16
+ analyzeBtn . onclick = togglePanel ;
17
+ targetArea . appendChild ( analyzeBtn ) ;
18
+
19
+ // inject panel
20
+ fetch ( chrome . runtime . getURL ( 'panel.html' ) )
21
+ . then ( response => response . text ( ) )
22
+ . then ( html => {
23
+ document . body . insertAdjacentHTML ( 'beforeend' , html ) ;
24
+ panel = document . getElementById ( 'panel' ) ;
25
+ setupPanelBtn ( ) ;
26
+ } ) . catch ( err => console . error ( 'Failed to load panel HTML:' , err ) ) ;
27
+
28
+ isUIRunning = true ;
29
+ }
30
+ }
31
+
32
+ // toggle panel
33
+ function togglePanel ( ) {
34
+ if ( ! panel ) return ;
35
+ panel . classList . toggle ( 'visible' ) ;
36
+ }
37
+
38
+ // setup panel button
39
+ function setupPanelBtn ( ) {
40
+ const closeBtn = document . getElementById ( 'close-btn' ) ;
41
+ closeBtn . addEventListener ( 'click' , togglePanel ) ;
42
+
43
+ const revealBtn = document . querySelectorAll ( '.btn' ) ;
44
+ revealBtn . forEach ( button => {
45
+ button . addEventListener ( 'click' , handleRevealBtn ) ;
46
+ } ) ;
47
+
48
+ const retryBtns = document . querySelectorAll ( '.btn-retry' ) ;
49
+ retryBtns . forEach ( button => {
50
+ button . addEventListener ( 'click' , handleRevealBtn ) ;
51
+ } ) ;
52
+ }
53
+
54
+
55
+ // handle reveal button click
56
+ function handleRevealBtn ( event ) {
57
+ const button = event . target ;
58
+ const stage = button . dataset . stage ;
59
+
60
+ let problemTitle = '' ;
61
+ let problemDescription = '' ;
62
+
63
+ // find title using different methods
64
+ const titleSelectors = [
65
+ '.text-title-large a' , 'div[data-cy="question-title"]' , '.mr-2.text-label-1'
66
+ ] ;
67
+ let title = null ;
68
+ for ( const selector of titleSelectors ) {
69
+ title = document . querySelector ( selector ) ;
70
+ if ( title ) {
71
+ problemTitle = title . innerText ;
72
+ break ;
73
+ }
74
+ }
75
+
76
+ // find description element
77
+ const descSelectors = [
78
+ 'div[data-track-load="description_content"]' ,
79
+ 'div[class*="elfjS"]' ,
80
+ 'div.prose' ,
81
+ 'div[class^="description__"]' ,
82
+ 'div[class*="question-content"]'
83
+ ] ;
84
+ let description = null ;
85
+ for ( const selector of descSelectors ) {
86
+ description = document . querySelector ( selector ) ;
87
+ if ( description ) {
88
+ problemDescription = description . innerText ;
89
+ break ;
90
+ }
91
+ }
92
+
93
+ if ( ! problemTitle || ! problemDescription ) {
94
+ console . error ( "Error Info:" , {
95
+ titleFound : ! ! problemTitle ,
96
+ descriptionFound : ! ! problemDescription ,
97
+ url : window . location . href
98
+ } ) ;
99
+ alert ( "LeetCode Assistant Error: Could not find the problem title or description on the page. LeetCode may have updated its layout. Please check the browser console for more details." ) ;
100
+
101
+ // try again
102
+ const revealBtn = document . querySelector ( `.btn[data-stage="${ stage } "]` ) ;
103
+ if ( revealBtn ) {
104
+ revealBtn . textContent = `Reveal ${ stage . charAt ( 0 ) . toUpperCase ( ) + stage . slice ( 1 ) } ` ;
105
+ revealBtn . disabled = false ;
106
+ }
107
+ return ;
108
+ }
109
+
110
+
111
+ const problemText = `Title: ${ problemTitle } \n\nDescription:\n${ problemDescription } ` ;
112
+
113
+ // --- hide retry and show loading ---
114
+ const retryBtn = document . querySelector ( `.btn-retry[data-stage="${ stage } "]` ) ;
115
+ if ( retryBtn ) {
116
+ retryBtn . style . display = 'none' ;
117
+ }
118
+ const revealBtn = document . querySelector ( `.btn[data-stage="${ stage } "]` ) ;
119
+ if ( revealBtn ) {
120
+ revealBtn . textContent = 'Loading...' ;
121
+ revealBtn . disabled = true ;
122
+ revealBtn . style . display = 'inline-block' ;
123
+ }
124
+
125
+ // send request to background.js
126
+ chrome . runtime . sendMessage ( {
127
+ type : 'getAnalysis' ,
128
+ stage : stage ,
129
+ problem : problemText
130
+ } ) ;
131
+ }
132
+
133
+
134
+ // receive response from background.js script
135
+ chrome . runtime . onMessage . addListener ( ( message , sender , sendResponse ) => {
136
+ if ( message . type === 'analysisResult' ) {
137
+ const { stage, content } = message ;
138
+
139
+ const contentDiv = document . getElementById ( `${ stage } -content` ) ;
140
+ const button = document . querySelector ( `.btn[data-stage="${ stage } "]` ) ;
141
+ const retryBtn = document . querySelector ( `.btn-retry[data-stage="${ stage } "]` ) ;
142
+
143
+ if ( contentDiv && button && retryBtn ) {
144
+ if ( content . startsWith ( 'Error:' ) ) {
145
+ contentDiv . innerHTML = `<p class="error-message">${ content } </p>` ;
146
+ contentDiv . style . display = 'block' ;
147
+ button . style . display = 'none' ;
148
+ retryBtn . style . display = 'inline-block' ;
149
+ } else {
150
+ contentDiv . innerHTML = formatMarkdown ( content ) ;
151
+ contentDiv . style . display = 'block' ;
152
+ button . style . display = 'none' ;
153
+ retryBtn . style . display = 'none' ;
154
+ }
155
+ }
156
+ }
157
+ } ) ;
158
+
159
+
160
+ // --- markdown formatting ---
161
+ function formatMarkdown ( text ) {
162
+ let Text = text . replace ( / ` ` ` ( \w * ) \n ( [ \s \S ] * ?) ` ` ` / g, ( match , lang , code ) => {
163
+ const language = lang || 'plaintext' ;
164
+ return `<pre><code class="language-${ language } ">${ code . trim ( ) } </code></pre>` ;
165
+ } ) ;
166
+
167
+ // Headings
168
+ Text = Text . replace ( / ^ # # # ( .* $ ) / gim, '<h3>$1</h3>' ) ;
169
+ Text = Text . replace ( / ^ # # ( .* $ ) / gim, '<h2>$1</h2>' ) ;
170
+ Text = Text . replace ( / ^ # ( .* $ ) / gim, '<h1>$1</h1>' ) ;
171
+
172
+ // Bold text
173
+ Text = Text . replace ( / \* \* ( .* ?) \* \* / g, '<strong>$1</strong>' ) ;
174
+
175
+ // Unordered list (grouped)
176
+ Text = Text . replace ( / (?: ^ | \n ) [ * - ] ( .* ?) (? = \n | $ ) / g, ( _ , item ) => `<li>${ item } </li>` ) ;
177
+ Text = Text . replace ( / ( < l i > [ \s \S ] * ?< \/ l i > ) / g, '<ul>$1</ul>' ) ;
178
+ Text = Text . replace ( / < \/ u l > \s * < u l > / g, '' ) ;
179
+
180
+ // Ordered list (grouped)
181
+ Text = Text . replace ( / (?: ^ | \n ) \d + \. ( .* ?) (? = \n | $ ) / g, ( _ , item ) => `<li>${ item } </li>` ) ;
182
+ Text = Text . replace ( / ( < l i > [ \s \S ] * ?< \/ l i > ) / g, '<ol>$1</ol>' ) ;
183
+ Text = Text . replace ( / < \/ o l > \s * < o l > / g, '' ) ;
184
+
185
+ // Paragraphs
186
+ Text = Text . split ( '\n' ) . map ( p => {
187
+ if ( p . trim ( ) === '' || p . startsWith ( '<h' ) || p . startsWith ( '<ul' ) || p . startsWith ( '<ol' ) || p . startsWith ( '<pre' ) ) {
188
+ return p ;
189
+ }
190
+ return `<p>${ p } </p>` ;
191
+ } ) . join ( '' ) ;
192
+
193
+ return Text ;
194
+ }
195
+
196
+
197
+ // detect changes in the page
198
+ const detect = new MutationObserver ( ( mutations ) => {
199
+ if ( window . location . href . includes ( '/problems/' ) && ! document . getElementById ( 'analyze-btn' ) ) {
200
+ // delay for load completely
201
+ setTimeout ( main , 1000 ) ;
202
+ }
203
+ } ) ;
204
+ detect . observe ( document . body , { childList : true , subtree : true } ) ;
205
+
206
+
207
+ // run Extension
208
+ setTimeout ( main , 1500 ) ;
0 commit comments