@@ -82,6 +82,9 @@ document.addEventListener("DOMContentLoaded", function () {
8282 const EMOJI_API_URL = 'https://api.github.com/emojis' ;
8383 let emojiLoadPromise = null ;
8484 let emojiEntries = [ ] ;
85+ let emojiUrlMap = new Map ( ) ;
86+ let emojiLookupLoaded = false ;
87+ let emojiRenderScheduled = false ;
8588 let emojiItems = [ ] ;
8689 const emojiSelection = new Set ( ) ;
8790 let symbolItems = [ ] ;
@@ -1493,6 +1496,20 @@ This is a fully client-side application. Your content never leaves your browser
14931496 }
14941497 }
14951498
1499+ function scheduleEmojiLookupRefresh ( ) {
1500+ if ( emojiLookupLoaded || emojiRenderScheduled ) return ;
1501+ emojiRenderScheduled = true ;
1502+ loadEmojiEntries ( )
1503+ . then ( ( ) => {
1504+ if ( emojiUrlMap . size ) {
1505+ renderMarkdown ( ) ;
1506+ }
1507+ } )
1508+ . finally ( ( ) => {
1509+ emojiRenderScheduled = false ;
1510+ } ) ;
1511+ }
1512+
14961513 function processEmojis ( element ) {
14971514 const walker = document . createTreeWalker (
14981515 element ,
@@ -1519,36 +1536,59 @@ This is a fully client-side application. Your content never leaves your browser
15191536 }
15201537 }
15211538
1539+ let needsEmojiLookup = false ;
15221540 textNodes . forEach ( textNode => {
15231541 const text = textNode . nodeValue ;
15241542 const emojiRegex = / : ( [ \w + - ] + ) : / g;
15251543
15261544 let match ;
15271545 let lastIndex = 0 ;
1528- let result = '' ;
15291546 let hasEmoji = false ;
1547+ const fragment = document . createDocumentFragment ( ) ;
15301548
15311549 while ( ( match = emojiRegex . exec ( text ) ) !== null ) {
15321550 const shortcode = match [ 1 ] ;
15331551 const emoji = joypixels . shortnameToUnicode ( `:${ shortcode } :` ) ;
15341552
15351553 if ( emoji !== `:${ shortcode } :` ) { // If conversion was successful
15361554 hasEmoji = true ;
1537- result += text . substring ( lastIndex , match . index ) + emoji ;
1555+ if ( match . index > lastIndex ) {
1556+ fragment . appendChild ( document . createTextNode ( text . substring ( lastIndex , match . index ) ) ) ;
1557+ }
1558+ fragment . appendChild ( document . createTextNode ( emoji ) ) ;
15381559 lastIndex = emojiRegex . lastIndex ;
15391560 } else {
1540- result += text . substring ( lastIndex , emojiRegex . lastIndex ) ;
1541- lastIndex = emojiRegex . lastIndex ;
1561+ const emojiUrl = emojiUrlMap . get ( shortcode ) ;
1562+ if ( emojiUrl ) {
1563+ hasEmoji = true ;
1564+ if ( match . index > lastIndex ) {
1565+ fragment . appendChild ( document . createTextNode ( text . substring ( lastIndex , match . index ) ) ) ;
1566+ }
1567+ const image = document . createElement ( 'img' ) ;
1568+ image . className = 'emoji-inline' ;
1569+ image . src = emojiUrl ;
1570+ image . alt = `:${ shortcode } :` ;
1571+ image . loading = 'lazy' ;
1572+ image . setAttribute ( 'aria-label' , `:${ shortcode } :` ) ;
1573+ fragment . appendChild ( image ) ;
1574+ lastIndex = emojiRegex . lastIndex ;
1575+ } else if ( ! emojiLookupLoaded ) {
1576+ needsEmojiLookup = true ;
1577+ }
15421578 }
15431579 }
15441580
15451581 if ( hasEmoji ) {
1546- result += text . substring ( lastIndex ) ;
1547- const span = document . createElement ( 'span' ) ;
1548- span . innerHTML = result ;
1549- textNode . parentNode . replaceChild ( span , textNode ) ;
1582+ if ( lastIndex < text . length ) {
1583+ fragment . appendChild ( document . createTextNode ( text . substring ( lastIndex ) ) ) ;
1584+ }
1585+ textNode . parentNode . replaceChild ( fragment , textNode ) ;
15501586 }
15511587 } ) ;
1588+
1589+ if ( needsEmojiLookup ) {
1590+ scheduleEmojiLookupRefresh ( ) ;
1591+ }
15521592 }
15531593
15541594 function debouncedRender ( ) {
@@ -2115,11 +2155,15 @@ This is a fully client-side application. Your content never leaves your browser
21152155 shortcode : `:${ name } :` ,
21162156 search : `${ name } :${ name } :` . toLowerCase ( ) ,
21172157 } ) ) ;
2158+ emojiUrlMap = new Map ( emojiEntries . map ( ( entry ) => [ entry . name , entry . url ] ) ) ;
2159+ emojiLookupLoaded = true ;
21182160 return emojiEntries ;
21192161 } )
21202162 . catch ( ( error ) => {
21212163 console . error ( 'Failed to load GitHub emojis:' , error ) ;
21222164 emojiEntries = [ ] ;
2165+ emojiUrlMap = new Map ( ) ;
2166+ emojiLookupLoaded = true ;
21232167 return emojiEntries ;
21242168 } ) ;
21252169 return emojiLoadPromise ;
@@ -2511,7 +2555,8 @@ This is a fully client-side application. Your content never leaves your browser
25112555
25122556 const alertTypes = [ 'note' , 'tip' , 'important' , 'warning' , 'caution' ] ;
25132557 let selectedType = alertTypes [ 0 ] ;
2514- const options = alertTypes . map ( ( type ) => {
2558+ const options = [ ] ;
2559+ alertTypes . forEach ( ( type ) => {
25152560 const meta = GITHUB_ALERT_META [ type ] || { label : type } ;
25162561 const option = document . createElement ( 'button' ) ;
25172562 option . type = 'button' ;
@@ -2531,8 +2576,8 @@ This is a fully client-side application. Your content never leaves your browser
25312576 item . setAttribute ( 'aria-pressed' , isSelected . toString ( ) ) ;
25322577 } ) ;
25332578 } ) ;
2579+ options . push ( option ) ;
25342580 grid . appendChild ( option ) ;
2535- return option ;
25362581 } ) ;
25372582
25382583 function insertAlert ( ) {
0 commit comments