User:Rusalkii/previewRedirectContext.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// @ts-check
//Shows the paragraph containing the redirect term in the target article
//Also searches Wikipedia for other articles containing the redirect term

( function () {
    'use strict';

    // Only run on redirect pages
    if ( !mw.config.get( 'wgIsRedirect' ) ) {
        return;
    }

    mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
    	
    	let autoRun = true; //user-defined pref, defaults to true
    	if (typeof(autoRunRedirectContext) != "undefined" && autoRunRedirectContext == false) {
    		autoRun = false;
    	}
        
        if ( autoRun ) {
            runContextFinder();
        } else {
            addButton();
        }

        function addButton() {
            // Create button
            const button = document.createElement( 'button' );
            button.textContent = 'Find in target';
            button.style.cssText = 'margin-left: 10px; padding: 6px 12px; background-color: #3366cc; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; vertical-align: middle;';
            button.onmouseover = function() { this.style.backgroundColor = '#2952a3'; };
            button.onmouseout = function() { this.style.backgroundColor = '#3366cc'; };
            
            button.addEventListener( 'click', function() {
                button.disabled = true;
                runContextFinder();
            } );

            // Insert after redirect message (inline)
            const redirectMsg = document.querySelector( '.redirectMsg' );
            if ( redirectMsg ) {
                redirectMsg.appendChild( button );
            }
        }

        function runContextFinder() {
            const api = new mw.Api( {
                ajax: {
                    headers: {
                        'Api-User-Agent': 'RedirectContextFinder/1.0'
                    }
                }
            } );

            // Get redirect target
            api.get( {
                action: 'query',
                prop: 'info',
                titles: mw.config.get( 'wgPageName' ),
                redirects: 1
            } ).then( function ( data ) {
                const pages = data.query.pages;
                const redirects = data.query.redirects;

                if ( !redirects || redirects.length === 0 ) {
                    return;
                }

                const targetTitle = redirects[ 0 ].to;
                const redirectTitle = mw.config.get( 'wgTitle' );

                // Fetch target page content
                api.get( {
                    action: 'query',
                    prop: 'revisions',
                    titles: targetTitle,
                    rvprop: 'content',
                    rvslots: 'main',
                    formatversion: 2
                } ).then( function ( data ) {
                	// Create container for results
                    const container = document.createElement( 'div' );
	                container.className = 'redirect-context-finder';
	                container.style.cssText = 'margin: 20px 0; padding: 15px; border: 2px solid #3366cc; background-color: #f0f8ff; border-radius: 5px;';

                    // Insert after redirect message
                	const redirectMsg = document.querySelector( '.redirectMsg' );
                	redirectMsg.parentNode.insertBefore( container, redirectMsg.nextSibling );

                    if ( !data.query.pages[ 0 ].revisions ) {
                        container.innerHTML = '<strong style="color: #cc0000;">Error: Could not load target page</strong>';
                        return;
                    }

                    const wikitext = data.query.pages[ 0 ].revisions[ 0 ].slots.main.content;
                    const result = findRedirectContext( redirectTitle, wikitext );

                    if ( result ) {
                        container.innerHTML = '<div style="background: white; padding: 10px; margin-top: 10px;">' +
                            result.paragraph +
                            '</div>';
                    } else { 
                    	 container.innerHTML = '<div style="margin-top: 10px; color: #666;">' +
                        '<strong style="color: #cc0000;">✗</strong> "<strong>' + mw.html.escape( redirectTitle ) + '</strong>" does not appear in the target. ' +
                        '</div>';
                    }

                    // Add search results section
                    const searchContainer = document.createElement( 'div' );
                    searchContainer.style.cssText = 'margin-top: 15px; padding-top: 15px; border-top: 1px solid #3366cc;';
                    container.appendChild( searchContainer );

                    // Perform Wikipedia search
                    searchWikipedia( api, redirectTitle, targetTitle, searchContainer );

                } ).catch( function () {
                    const container = document.createElement( 'div' );
                    container.innerHTML = '<strong style="color: #cc0000;">Error loading target page</strong>';
                    const redirectMsg = document.querySelector( '.redirectMsg' );
                    if ( redirectMsg ) {
                        redirectMsg.parentNode.insertBefore( container, redirectMsg.nextSibling );
                    }
                } );
            } ).catch( function () {
                // Silently fail if we can't get redirect info
            } );
        }

        function searchWikipedia( api, searchTerm, targetTitle, container ) {
            api.get( {
                action: 'query',
                list: 'search',
                srsearch: '"' + searchTerm + '"',
                srnamespace: 0,
                srlimit: 5,
                srwhat: 'text',
                srprop: 'snippet|titlesnippet'
            } ).then( function ( data ) {
                const results = data.query.search;
                
                // Filter out the target article itself
                const otherArticles = results.filter( function( result ) {
                    return result.title !== targetTitle;
                } );

                const totalResults = data.query.searchinfo.totalhits;
                const otherCount = Math.max(0, totalResults - 1); // Exclude target article

                let html = '<div class="mw-search-results-container">';
                html += '<strong>Search Results:</strong> ';
                
                if ( otherCount === 0 ) {
                    html += '"<strong>' + mw.html.escape( searchTerm ) + '</strong>" was not found in any other articles.';
                    html += '</div>';
                } else {
                    html += 'Found in <strong>' + otherCount + '</strong> other article' + (otherCount !== 1 ? 's' : '');
                    html += '</div>';
                    html += '<ul class="mw-search-results">';
                    
                    for ( let i = 0; i < otherArticles.length; i++ ) {
                        const article = otherArticles[i];
                        const articleUrl = mw.util.getUrl( article.title );
                        
                        html += '<li class="mw-search-result">';
                        html += '<a href="' + articleUrl + '" title="' + mw.html.escape( article.title ) + '">';
                        html += mw.html.escape( article.title );
                        html += '</a>';
                        html += '</div>';
                        
                        // Highlight the search term in snippets
                        if ( article.snippet ) {
                            let highlightedSnippet = article.snippet.replace( 
                                /<span class="searchmatch">(.*?)<\/span>/gi, 
                                '<mark style="background-color: #ffffcc; font-weight: normal;">$1</mark>' 
                            );
                            html += '<div class="searchresult">' + highlightedSnippet + '</div>';
                        }
                        
                        html += '</li>';
                    }
                    
                    html += '</ul>';
                    
                    if ( otherCount > 5 ) {
                    	
                        const searchUrl = mw.util.getUrl( 'Special:Search', { search: searchTerm } ) + '&fulltext=1&ns0=1';
                        html += '<div style="margin-top: 10px;"><a href="' + searchUrl + '">View all ' + 
                                otherCount + ' results →</a></div>';
                    }
                    
                    html += '</div>';
                }
                
                container.innerHTML = html;

            } ).catch( function () {
                container.innerHTML = '<div style="margin-top: 10px; color: #cc0000;">Error performing search</div>';
            } );
        }
        
		function createRegex( term ) {
		    // Create a version without parenthetical content
		    const termWithoutParens = term.replace(/\s*\([^)]*\)\s*/g, '').trim();
		    
		    function createPattern(str) {
		        // Escape special regex characters except spaces and hyphens
		        let pattern = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
		        
		        // Replace spaces with flexible whitespace/punctuation pattern
		        pattern = pattern.replace(/\s+/g, '[\\s\\-–—]*');
		        
		        // Replace hyphens/dashes with flexible dash pattern (matches -, –, —, or absence)
		        pattern = pattern.replace(/[\-–—]/g, '[\\s\\-–—]*');
		        
		        // Replace punctuation with optional pattern
		        pattern = pattern.replace(/\./g, '\\.?');
		        pattern = pattern.replace(/!/g, '!?');
		        pattern = pattern.replace(/,/g, ',?');
		        pattern = pattern.replace(/'/g, '[\']?');
		        pattern = pattern.replace(/"/g, '[""]?');
		        
		        return pattern;
		    }
		    
		    const patterns = [createPattern(term)];
		    
		    // Only add the version without parens if it's different
		    if (termWithoutParens !== term && termWithoutParens.length > 0) {
		        patterns.push(createPattern(termWithoutParens));
		    }
		    
		    return new RegExp('(' + patterns.join('|') + ')', 'gi');
		}


        function findRedirectContext( searchTerm, wikitext ) {
        	let regex = createRegex(searchTerm);
            const paragraphs = wikitext.split( /\n\n+/ );
            
        	console.log(regex);
            for ( let i = 0; i < paragraphs.length; i++ ) {
                const para = paragraphs[ i ];

                // Check if paragraph contains the search term
                if ( para.match(regex) ) {
                    let cleaned = para
                        .replace( /<ref[^>]*>.*?<\/ref>/gi, '' ) // Remove refs
                        .replace( /<ref[^>]*\/>/gi, '' ) // Remove self-closing refs
                        .trim();

                    // Highlight the search term
                    cleaned = cleaned.replace( regex, '<mark style="background-color: #ffff00; font-weight: bold;">$1</mark>' );

                    return {
                        paragraph: cleaned,
                    };
                }
            }

            return null;
        }
    } );
}() );