1+ const EMOJI_SELECTORS = [
2+ '.Emoji_emoji-large__GG4kj' ,
3+ '[data-emoji-char]' ,
4+ '.emoji-large'
5+ ] ;
6+
7+ const STYLES = {
8+ box : {
9+ light : {
10+ backgroundColor : '#f8f9fa' ,
11+ borderColor : '#ccc' ,
12+ color : 'inherit'
13+ } ,
14+ dark : {
15+ backgroundColor : '#303134' ,
16+ borderColor : '#5f6368' ,
17+ color : '#e8eaed'
18+ }
19+ }
20+ } ;
21+
22+ function showUserError ( searchResult , message ) {
23+ if ( searchResult . querySelector ( '.emoji-error-box' ) ) return ;
24+
25+ const errorBox = document . createElement ( 'div' ) ;
26+ errorBox . className = 'emoji-error-box' ;
27+
28+ const isDark = window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ;
29+ const theme = isDark ? STYLES . box . dark : STYLES . box . light ;
30+
31+ errorBox . style . cssText = `
32+ width: 100%;
33+ display: flex;
34+ justify-content: center;
35+ align-items: center;
36+ gap: 8px;
37+ padding: 8px 12px;
38+ margin-top: 0px;
39+ margin-bottom: 20px;
40+ border-radius: 6px;
41+ font-size: 14px;
42+ border: 1px solid #ea4335;
43+ background-color: ${ isDark ? '#2d1b1e' : '#fce8e6' } ;
44+ color: #ea4335;
45+ box-sizing: border-box;
46+ ` ;
47+
48+ errorBox . innerHTML = `
49+ <span>⚠️</span>
50+ <span>${ message } </span>
51+ ` ;
52+
53+ searchResult . appendChild ( errorBox ) ;
54+
55+ setTimeout ( ( ) => {
56+ if ( errorBox . parentNode ) {
57+ errorBox . parentNode . removeChild ( errorBox ) ;
58+ }
59+ } , 5000 ) ;
60+ }
61+
162async function getEmojiFromEmojipedia ( url ) {
263 return new Promise ( ( resolve , reject ) => {
364 chrome . runtime . sendMessage ( { type : "fetchEmojipedia" , url } , ( response ) => {
4- if ( response . error ) {
5- reject ( response . error ) ;
65+ if ( chrome . runtime . lastError ) {
66+ reject ( `Connection error: ${ chrome . runtime . lastError . message } ` ) ;
67+ return ;
68+ }
69+
70+ if ( response ?. error ) {
71+ reject ( `Fetch error: ${ response . error } ` ) ;
672 return ;
773 }
874
9- const parser = new DOMParser ( ) ;
10- const doc = parser . parseFromString ( response . html , 'text/html' ) ;
75+ if ( ! response ?. html ) {
76+ reject ( 'No content received from Emojipedia' ) ;
77+ return ;
78+ }
1179
12- const emojiElement = doc . querySelector ( '.Emoji_emoji-large__GG4kj' ) ;
80+ try {
81+ const parser = new DOMParser ( ) ;
82+ const doc = parser . parseFromString ( response . html , 'text/html' ) ;
1383
14- if ( emojiElement ) {
15- resolve ( emojiElement . innerText . trim ( ) ) ;
16- } else {
17- reject ( 'Emoji not found on page.' ) ;
84+ for ( const selector of EMOJI_SELECTORS ) {
85+ const emojiElement = doc . querySelector ( selector ) ;
86+ if ( emojiElement ?. textContent ?. trim ( ) ) {
87+ resolve ( emojiElement . textContent . trim ( ) ) ;
88+ return ;
89+ }
90+ }
91+
92+ reject ( 'Emoji not found on page' ) ;
93+ } catch ( error ) {
94+ reject ( `Page parsing failed: ${ error . message } ` ) ;
1895 }
1996 } ) ;
2097 } ) ;
2198}
2299
23100function createBox ( emoji , searchResult ) {
24- if ( ! searchResult ) return ;
25-
26- if ( searchResult . querySelector ( '.emoji-copy-box' ) ) return ;
27-
28- const box = document . createElement ( 'div' ) ;
29- box . className = 'emoji-copy-box' ;
30- box . style . cssText = `
31- width: 100%;
32- display: flex;
33- justify-content: center;
34- align-items: center;
35- gap: 12px;
36- padding: 8px 12px;
37- margin-top: 0px;
38- margin-bottom: 20px;
39- border-radius: 6px;
40- font-size: 26px;
41- border: 1px solid #ccc;
42- background-color: #f8f9fa;
43- box-sizing: border-box;
44- color: inherit;
45- ` ;
46-
47- const isDark = window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ;
48- if ( isDark ) {
49- box . style . backgroundColor = '#303134' ;
50- box . style . borderColor = '#5f6368' ;
51- box . style . color = '#e8eaed' ;
52- }
53-
54- const emojiSpan = document . createElement ( 'span' ) ;
55- emojiSpan . textContent = emoji ;
56-
57- const copyButton = document . createElement ( 'button' ) ;
58- copyButton . textContent = 'Copy' ;
59- copyButton . style . cssText = `
60- background-color: #1a73e8;
61- border: none;
62- color: white;
63- padding: 6px 14px;
64- border-radius: 6px;
65- cursor: pointer;
66- font-size: 16px;
67- margin-left: 10px;
68- ` ;
69-
70- copyButton . addEventListener ( 'click' , ( ) => {
71- navigator . clipboard . writeText ( emoji ) . then ( ( ) => {
72- copyButton . textContent = 'Copied!' ;
73- setTimeout ( ( ) => {
74- copyButton . textContent = 'Copy' ;
75- } , 1200 ) ;
76- } ) . catch ( err => {
77- console . error ( 'Clipboard error:' , err ) ;
78- copyButton . textContent = 'Error' ;
101+ if ( ! searchResult || ! emoji ) return ;
102+
103+ if ( searchResult . querySelector ( '.emoji-copy-box' ) ) return ;
104+
105+ const box = document . createElement ( 'div' ) ;
106+ box . className = 'emoji-copy-box' ;
107+
108+ const isDark = window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ;
109+ const theme = isDark ? STYLES . box . dark : STYLES . box . light ;
110+
111+ box . style . cssText = `
112+ width: 100%;
113+ display: flex;
114+ justify-content: center;
115+ align-items: center;
116+ gap: 12px;
117+ padding: 8px 12px;
118+ margin-top: 0px;
119+ margin-bottom: 20px;
120+ border-radius: 6px;
121+ font-size: 26px;
122+ border: 1px solid ${ theme . borderColor } ;
123+ background-color: ${ theme . backgroundColor } ;
124+ box-sizing: border-box;
125+ color: ${ theme . color } ;
126+ ` ;
127+
128+ const emojiSpan = document . createElement ( 'span' ) ;
129+ emojiSpan . textContent = emoji ;
130+
131+ const copyButton = document . createElement ( 'button' ) ;
132+ copyButton . textContent = 'Copy' ;
133+ copyButton . style . cssText = `
134+ background-color: #1a73e8;
135+ border: none;
136+ color: white;
137+ padding: 6px 14px;
138+ border-radius: 6px;
139+ cursor: pointer;
140+ font-size: 16px;
141+ margin-left: 10px;
142+ transition: background-color 0.2s ease;
143+ ` ;
144+
145+ copyButton . addEventListener ( 'mouseenter' , ( ) => {
146+ copyButton . style . backgroundColor = '#1557b0' ;
147+ } ) ;
148+
149+ copyButton . addEventListener ( 'mouseleave' , ( ) => {
150+ copyButton . style . backgroundColor = '#1a73e8' ;
151+ } ) ;
152+
153+ copyButton . addEventListener ( 'click' , async ( ) => {
154+ try {
155+ await navigator . clipboard . writeText ( emoji ) ;
156+ const originalText = copyButton . textContent ;
157+ copyButton . textContent = 'Copied!' ;
158+ copyButton . style . backgroundColor = '#34a853' ;
159+
160+ setTimeout ( ( ) => {
161+ copyButton . textContent = originalText ;
162+ copyButton . style . backgroundColor = '#1a73e8' ;
163+ } , 1200 ) ;
164+ } catch ( err ) {
165+ console . error ( 'Clipboard error:' , err ) ;
166+ copyButton . textContent = 'Failed' ;
167+ copyButton . style . backgroundColor = '#ea4335' ;
168+
169+ setTimeout ( ( ) => {
170+ copyButton . textContent = 'Copy' ;
171+ copyButton . style . backgroundColor = '#1a73e8' ;
172+ } , 1200 ) ;
173+ }
79174 } ) ;
80- } ) ;
81175
82- box . appendChild ( emojiSpan ) ;
83- box . appendChild ( copyButton ) ;
176+ box . appendChild ( emojiSpan ) ;
177+ box . appendChild ( copyButton ) ;
178+ searchResult . appendChild ( box ) ;
179+ }
84180
85- searchResult . appendChild ( box ) ;
181+ function processSearchResults ( ) {
182+ const resultLinks = document . querySelectorAll ( '.zReHs' ) ;
183+
184+ resultLinks . forEach ( ( link ) => {
185+ const searchResult = link . closest ( '.MjjYud' ) ;
186+ if ( ! searchResult ) return ;
187+
188+ const firstElement = searchResult . firstElementChild ?. firstElementChild ;
189+ if ( ! firstElement || firstElement . hasAttribute ( 'data-initq' ) ) return ;
190+
191+ if ( link . href ?. startsWith ( "https://emojipedia.org" ) ) {
192+ getEmojiFromEmojipedia ( link . href )
193+ . then ( emoji => createBox ( emoji , searchResult ) )
194+ . catch ( err => {
195+ console . warn ( 'Failed to fetch emoji:' , err ) ;
196+ showUserError ( searchResult , 'Failed to load emoji' ) ;
197+ } ) ;
198+ }
199+ } ) ;
86200}
87201
88- chrome . storage . local . get ( [ 'enabled' ] , ( data ) => {
89- if ( data . enabled !== false ) {
90- window . addEventListener ( 'DOMContentLoaded' , function ( ) {
91- // const prompt = document.getElementById('APjFqb').value.toLowerCase();
92- // if (prompt.includes('emoji')) {
93- const resultLinks = document . getElementsByClassName ( 'zReHs' ) ;
94-
95- Array . from ( resultLinks ) . forEach ( ( link ) => {
96- let searchResult = link . closest ( '.MjjYud' ) ;
97- if ( searchResult ) {
98-
99- let n = searchResult . firstElementChild ?. firstElementChild
100- if ( n && ! n . hasAttribute ( 'data-initq' ) ) {
101- if ( link . href . startsWith ( "https://emojipedia.org" ) ) {
102- getEmojiFromEmojipedia ( link . href )
103- . then ( emoji => createBox ( emoji , searchResult ) )
104- . catch ( err => console . error ( 'Error:' , err ) ) ;
105- }
106- }
202+ function initializeExtension ( ) {
203+ chrome . storage . local . get ( [ 'enabled' ] , ( data ) => {
204+ if ( data . enabled !== false ) {
205+ if ( document . readyState === 'loading' ) {
206+ document . addEventListener ( 'DOMContentLoaded' , processSearchResults ) ;
207+ } else {
208+ processSearchResults ( ) ;
209+ }
210+
211+ const observer = new MutationObserver ( ( mutations ) => {
212+ let shouldProcess = false ;
213+ mutations . forEach ( ( mutation ) => {
214+ if ( mutation . type === 'childList' && mutation . addedNodes . length > 0 ) {
215+ shouldProcess = true ;
107216 }
108217 } ) ;
109- // }
110- } ) ;
111- }
112- } ) ;
218+
219+ if ( shouldProcess ) {
220+ setTimeout ( processSearchResults , 100 ) ;
221+ }
222+ } ) ;
223+
224+ observer . observe ( document . body , {
225+ childList : true ,
226+ subtree : true
227+ } ) ;
228+ }
229+ } ) ;
230+ }
231+
232+ if ( typeof chrome !== 'undefined' && chrome . storage ) {
233+ initializeExtension ( ) ;
234+ }
0 commit comments