User:Polygnotus/Scripts/WikiBlame3.js
Appearance
From Wikipedia, the free encyclopedia
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/WikiBlame3.
(function(){ 'use strict'; // Only run in mainspace if(!mw.config.get('wgIsArticle')){ return; } constDEFAULT_HOTKEY='ctrl+shift+w'; letcurrentHotkeyBinding=null; // Storage functions functiongetStoredHotkey(){ try{ returnlocalStorage.getItem('wikiblame-hotkey')||DEFAULT_HOTKEY; }catch(e){ returnDEFAULT_HOTKEY; } } functionstoreHotkey(hotkey){ try{ localStorage.setItem('wikiblame-hotkey',hotkey); }catch(e){ console.warn('Could not store hotkey:',e); } } // WikiBlame search function functionperformWikiBlameSearch(){ constselectedText=window.getSelection().toString().trim(); if(!selectedText){ alert('Please select text first, then use WikiBlame search.'); return; } constlang=mw.config.get('wgContentLanguage'); constarticleTitle=mw.config.get('wgPageName'); constencodedNeedle=encodeURIComponent(selectedText); constwikiblameUrl=`https://wikipedia.ramselehof.de/wikiblame.php?user_lang=${lang}&lang=${lang}&project=wikipedia&tld=org&article=${articleTitle}&needle=${encodedNeedle}`; window.open(wikiblameUrl,'_blank'); } // Load Mousetrap library functionloadMousetrap(){ returnnewPromise((resolve,reject)=>{ if(window.Mousetrap){ resolve(); return; } constscript=document.createElement('script'); script.src='https://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.6.5/mousetrap.min.js'; script.onload=()=>{ if(window.Mousetrap){ resolve(); }else{ reject(newError('Mousetrap failed to load')); } }; script.onerror=()=>reject(newError('Failed to load Mousetrap')); document.head.appendChild(script); }); } // Register hotkey with Mousetrap functionregisterHotkey(hotkey){ if(!window.Mousetrap)return; // Unbind previous hotkey if(currentHotkeyBinding){ window.Mousetrap.unbind(currentHotkeyBinding); } try{ // Ensure we're working with a clean string format constcleanHotkey=typeofhotkey==='string'?hotkey:hotkey.toString(); // Normalize the hotkey format for Mousetrap constnormalizedHotkey=cleanHotkey.toLowerCase() .replace(/\s+/g,'')// Remove spaces .replace(/\+/g,'+') .replace(/ctrl/g,'ctrl') .replace(/alt/g,'alt') .replace(/shift/g,'shift') .replace(/meta/g,'meta'); window.Mousetrap.bind(normalizedHotkey,function(e){ // Prevent default browser behavior if(e.preventDefault){ e.preventDefault(); }else{ e.returnValue=false; } performWikiBlameSearch(); returnfalse; }); currentHotkeyBinding=normalizedHotkey; console.log('WikiBlame hotkey registered:',normalizedHotkey); }catch(e){ console.error('Failed to register hotkey:',hotkey,e); throwe; } } // Validate hotkey format functionisValidHotkey(hotkey){ // Basic validation for Mousetrap format constvalidPattern=/^(ctrl\+|alt\+|shift\+|meta\+)*[a-z0-9]$/i; constnormalizedHotkey=hotkey.toLowerCase().replace(/\s+/g,''); // Don't allow problematic combinations constproblematicKeys=['f12','ctrl+shift+i','ctrl+u','ctrl+r','ctrl+f','ctrl+s','ctrl+p']; if(problematicKeys.includes(normalizedHotkey)){ returnfalse; } returnvalidPattern.test(normalizedHotkey)||/^[f][1-9]$|^[f][1][0-2]$/.test(normalizedHotkey); } // Create configuration popup functionshowConfigPopup(){ // Remove existing popup if any constexistingPopup=document.getElementById('wikiblame-config-popup'); if(existingPopup){ existingPopup.remove(); } constoverlay=document.createElement('div'); overlay.id='wikiblame-config-popup'; overlay.innerHTML=` <div class="wikiblame-overlay"></div> <div class="wikiblame-popup"> <h3>Configure WikiBlame Hotkey</h3> <p>Current hotkey: <strong>${getStoredHotkey()}</strong></p> <div class="form-group"> <label for="hotkey-input">New Hotkey:</label> <input type="text" id="hotkey-input" placeholder="e.g., ctrl+shift+w" value="${getStoredHotkey()}"> <div class="help-text"> Examples: ctrl+shift+w, alt+b, ctrl+alt+w, shift+f1<br> <strong>Avoid:</strong> F12, Ctrl+Shift+I, Ctrl+U, Ctrl+R, Ctrl+F, Ctrl+S, Ctrl+P </div> </div> <div class="button-group"> <button id="hotkey-cancel">Cancel</button> <button id="hotkey-save">Save</button> </div> </div> `; // Add styles conststyle=document.createElement('style'); style.textContent=` #wikiblame-config-popup { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 10000; } .wikiblame-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); } .wikiblame-popup { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border: 2px solid #a2a9b1; border-radius: 8px; padding: 20px; min-width: 400px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; box-shadow: 0 4px 12px rgba(0,0,0,0.3); } .wikiblame-popup h3 { margin: 0 0 15px 0; color: #000; font-size: 16px; } .wikiblame-popup p { margin: 0 0 15px 0; color: #54595d; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 5px; font-weight: bold; color: #000; } .form-group input { width: 100%; padding: 8px; border: 1px solid #a2a9b1; border-radius: 4px; box-sizing: border-box; font-size: 14px; } .help-text { font-size: 12px; color: #72777d; margin-top: 5px; line-height: 1.4; } .button-group { text-align: right; } .button-group button { margin-left: 10px; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 14px; } #hotkey-cancel { border: 1px solid #a2a9b1; background: white; color: #000; } #hotkey-save { border: none; background: #0645ad; color: white; } #hotkey-cancel:hover { background: #f8f9fa; } #hotkey-save:hover { background: #0b57d0; } `; document.head.appendChild(style); document.body.appendChild(overlay); constinput=document.getElementById('hotkey-input'); constsaveBtn=document.getElementById('hotkey-save'); constcancelBtn=document.getElementById('hotkey-cancel'); constoverlayEl=document.querySelector('.wikiblame-overlay'); input.focus(); input.select(); functionclosePopup(){ constpopup=document.getElementById('wikiblame-config-popup'); if(popup)popup.remove(); if(style.parentNode)style.parentNode.removeChild(style); } functionsaveHotkey(){ constnewHotkey=input.value.trim().toLowerCase(); if(!newHotkey){ alert('Please enter a hotkey combination.'); return; } if(!isValidHotkey(newHotkey)){ alert('Invalid hotkey format or conflicts with browser shortcuts.\nUse format like "ctrl+shift+w" and avoid F12, Ctrl+Shift+I, etc.'); return; } try{ registerHotkey(newHotkey); storeHotkey(newHotkey); // Update tooltip consttoolsLink=document.getElementById('t-wikiblame'); if(toolsLink){ toolsLink.title=`Find the edit that added the selected text (${newHotkey})`; } alert(`Hotkey updated to: ${newHotkey}`); closePopup(); }catch(e){ alert('Failed to register hotkey. Please try a different combination.'); } } saveBtn.addEventListener('click',saveHotkey); cancelBtn.addEventListener('click',closePopup); overlayEl.addEventListener('click',closePopup); input.addEventListener('keydown',(e)=>{ if(e.key==='Enter'){ saveHotkey(); }elseif(e.key==='Escape'){ closePopup(); } }); } // Initialize mw.loader.using(['mediawiki.util']).then(()=>{ conststoredHotkey=getStoredHotkey(); // Add tools menu link consttoolsLink=mw.util.addPortletLink( 'p-tb', '#', 'WikiBlame search', 't-wikiblame', `Find the edit that added the selected text (${storedHotkey})` ); if(toolsLink){ toolsLink.addEventListener('click',(e)=>{ e.preventDefault(); performWikiBlameSearch(); }); } // Add configuration link to More menu constconfigLink=mw.util.addPortletLink( 'p-cactions', '#', 'WikiBlame Settings', 't-wikiblame-config', 'Configure WikiBlame hotkey' ); if(configLink){ configLink.addEventListener('click',(e)=>{ e.preventDefault(); showConfigPopup(); }); } // Load Mousetrap library and register hotkey loadMousetrap() .then(()=>{ registerHotkey(storedHotkey); }) .catch((error)=>{ console.warn('WikiBlame: Could not load Mousetrap library:',error); }); }); })();