User:Js/urldecoder.js: Difference between revisions

Content deleted Content added
Js (talk | contribs)
now button file:Link_go.png in both old and new toolbar; rm ajax request for namespaces cause vars are available for a long time now
Js (talk | contribs)
switch to jquery.textSelection; fix for secure links ../wikiPeda/meta/.. → meta.wikiMedia.org; misc improvements
Line 1:
if( wgAction=='edit' || wgAction=='submit' ) $(function(){
function urlDecoderButton(){
addFuncButton2('urlDecoder', urlDecoderRun, 'http://upload.wikimedia.org/wikipedia/commons/9/91/Link_go.png', 'Decode URL before cursor or all URLs in selected text', window.urlDecoderKey)
}
if (wgAction=='edit' || wgAction=='submit') addOnloadHook(urlDecoderButton)
 
if( ! $.fn.textSelection ) mw.loader.load([ 'jquery.textSelection' ])
addFuncButton22(
'urlDecoder',
urlDecoderRun,
'http://upload.wikimedia.org/wikipedia/commons/9/91/Link_go.png',
'Decode URL before cursor or all URLs in selected text',
window.urlDecoderKey)
})
 
/**/
function addFuncButton2(id, func, img, title, akey){
 
if (window.wgWikiEditorEnabledModules && wgWikiEditorEnabledModules.toolbar){ //new
 
var msg = {}; msg[id] = title; mw.usability.addMessages(msg)
function addFuncButton22(bId, bFunc, bIcon, bTitle, bKey){
// $j('#wpTextbox1').wikiEditor('addToToolbar', { section:'main', groups: {'ruwp':{}}})
 
$j('#wpTextbox1').wikiEditor('addToToolbar', {
// [[mw:Toolbar_customization]] is not helpful, plus a little delay onload is probably a good thing
section:'main', //groups: {'mytools':{}},
//setTimeout( $.fn.wikiEditor ? newToolbarBtn : oldToolbarBtn, 500 )
group:'insert', tools: {id:{
 
type:'button',
$.fn.wikiEditor ? newToolbarBtn() : oldToolbarBtn()
action: {type:'callback', execute: func},
labelMsg:id,
function newToolbarBtn(){
icon:img
var msg = {}; msg[bId] = bTitle; mw.messages.set(msg) // mw.usability.addMessages(msg) doesn't work
$('#wpTextbox1').wikiEditor('addToToolbar', {
section:'main', group:'insert', tools: {
bId:{
type: 'button',
action: {type:'callback', execute: bFunc},
labelMsg: bId,
icon: bIcon
}}})
}else{ //old
}
var tlb_ = document.getElementById('toolbar')
 
if (!tlb_) return
var i=document.createElement('img')
function oldToolbarBtn(){
i.className='mw-toolbar-custombutton'; i.id=id
 
i.onclick=func; i.src=img; i.title=title; i.alt=title.substr(0,3)
var appendCSSbtn = $('<img. class=mw-toolbar-custombutton {height:20px;id="' background-color:#bce;+ border:1pxbId outset+ #bce; margin:0 1px; cursor:pointer}'">')
.attr({ src: bIcon, title: bTitle, alt: bTitle.substr(0,3) })
tlb_.appendChild(i)
.css({ height:'20px', 'background-color':'#bce', border:'1px outset #bce', margin:'0 1px', cursor:'pointer'})
if (akey){ i.accessKey = akey; i.title += ' ['+akey+']'; updateTooltipAccessKeys([i]) }
.click(bFunc)
}
.appendTo('#toolbar')
}
if( bKey ){
updateTooltipAccessKeys( btn[0] )
btn.attr({ accesskey: bKey, title: bTitle + ' ['+bKey+']' })
}
}
}
/**/
 
 
 
 
function urlDecoderRun(){ //main function
 
var httpRegExp = '(https?:\\/\\/[^\\]\\[\\n\\r<>" ]+)' // except []<>"
var beforeCursor = new RegExp('(\\[{0,2})'+httpRegExp+'( +[^\\]\n]+)?\\]{0,2}$', 'i')
var localPrefix = WMPrefixes(unSecure(wgServer+wgScript))
var newText, linkSize, txtarea = document.editform.wpTextbox1
var isBeforeCursor = false
 
//WMF domains mess
if (document.selection) { //IE/Opera
//2nd-lvl domains; secure link: .../wikipedia/mediawiki, .../wikipedia/foundation
var scrollTop = document.documentElement.scrollTop
var wmDomain = {
txtarea.focus()
mediawiki: 'mw',
range = document.selection.createRange()
wikimediafoundation: 'foundation' }
if (!range.moveStart) return
//2nd-lvl domains with multiple languages; secure link: wikinews/en
if (range.text){
var wmDomainM = {
newText = processSelText(range.text)
wikipedia:'w',
}else { //no selection
wikibooks:'b',
if (!(rr=range.duplicate())) return
wikinews:'n',
rr.moveStart('character', - 1500)
wikiquote:'q',
linkSize = processBeforeCursor(rr.text)
wikisource:'s',
if (!linkSize) return
wikiversity:'v',
range.moveStart('character', - linkSize) //select matched
wiktionary:'wikt'}
}
//3rd-lvl domains on .wikimedia.org; however secure link is wikipedia/*
//replace text
var wmSubDomains = /^(meta|commons|incubator|species|strategy)$/
if (newText != range.text){
//Exceptions:
range.text = newText
// https:/.../wikipedia/sources/wiki/Main_Page - not recognized by script
if (navigator.userAgent.indexOf('MSIE') != -1) newText = newText.replace(/\r/g,'') //for IE: do not count \r
range.moveStart('character', - newText.length)
range.select()
}
document.documentElement.scrollTop = scrollTop //restore window scroll position
 
}else if (txtarea.selectionStart || txtarea.selectionStart == '0') { // Mozilla
var scrollTop = txtarea.scrollTop, txt = txtarea.value
txtarea.focus()
var startPos = txtarea.selectionStart, endPos = txtarea.selectionEnd
if (startPos != endPos){
newText = processSelText(txt.substring(startPos, endPos))
}else{ //no selection
linkSize = processBeforeCursor(txt.substring((endPos-1500>0?endPos-1500:0), endPos))
if (!linkSize) return
startPos = endPos - linkSize //select matched
}
//replace text
if (newText != txt.substring(startPos, endPos)){
txtarea.value = txt.substring(0, startPos) + newText + txt.substring(endPos, txtarea.value.length)
txtarea.selectionEnd = startPos + newText.length
txtarea.selectionStart = startPos
}
txtarea.scrollTop = scrollTop
}//end of main function
 
var httpRegExp = '(https?:\\/\\/[^\\]\\[\\n\\r<>" ]+)' // any chars except []<>" and \n and spaces
var localPrefix = WMPrefixes( unSecure(wgServer+wgScript) )
var oldText, newText, isBeforeCursor, colonNS
 
tbox = $('#wpTextbox1').focus()
oldText = tbox.textSelection( 'getSelection' )
 
 
if( oldText ){ //there was selection
 
var rx = RegExp('(\\[{0,2})' + httpRegExp + '([^\\]\\[\\n\\r]*?\\]\\]?)?', 'ig')
newText = oldText.replace(rx, simplifyMatched)
 
if( window.urlDecoderIntLinks ){
var ut = '(' + wgFormattedNamespaces[3].replace(/ /g,'_') + '|user_talk)' //both localized and canonical 'user_talk'
ut = RegExp ('\\[\\[' + ut.toLowerCase() + ':[^#]+$', 'i')
newText = newText.replace(/\[\[[^\]\|\n]+/g, function(lnk){
return ut.test(lnk) ? lnk : decodeAnchor(lnk) // skip user_talk, usually found in signatures
})
}
 
if( newText == oldText) return
 
 
}else{ //process text before cursor
 
function processBeforeCursor(str){//finds http:.* in string, returns its length and also newText var
isBeforeCursor = true
 
var pos = str.lastIndexOf('http://')
//move back enough characters
if (pos == -1) pos = str.lastIndexOf('https://')
var caretPos = tbox.textSelection('getCaretPosition')
if (pos == -1) return 0
var beginPos = caretPos - 2000
if (pos >= 2) str = str.substring(pos-2) //move left to include leading [s
if( beginPos < 0 ) beginPos = 0
var ma = str.match(beforeCursor) // result: (whole string)' '[', 'http:...', ' name]'
tbox.textSelection( 'setSelection', {start:beginPos, end:caretPos} )
if (!ma) return 0
oldText = tbox.textSelection( 'getSelection' )
if (ma[3]) //link with name: automatically add brackets
tbox.textSelection( 'setSelection', {start:caretPos, end:caretPos} )
 
//try to find http in oldText
var rx = new RegExp('(\\[{0,2})'+httpRegExp+'( +[^\\]\n]+)?\\]{0,2}$', 'i')
var ma = rx.exec( oldText ) // result: (whole string)' '[', 'http:...', ' name]'
if( !ma ) return
oldText = ma[0]
if( ma[3] ) //link with name: automatically add brackets
newText = simplifyMatched(ma[0], '[', ma[2], ma[3]+']')
else //just url: add closing bracket only if there is leading bracket
newText = simplifyMatched(ma[0], ma[1], ma[2], ma[1]?']':'')
return ma[0].length
}
 
if( oldText == newText ) return
function processSelText(txt){
tbox.textSelection( 'setSelection', {start: caretPos - oldText.length, end: caretPos} )
txt = txt.replace(RegExp('(\\[{0,2})' + httpRegExp + '([^\\]\\[\\n\\r]*?\\]\\]?)?', 'ig'),
 
simplifyMatched)
if (window.urlDecoderIntLinks){
var ut = 'user_talk' //skip user_talk, usually in sig
if (window.wgFormattedNamespaces) ut = wgFormattedNamespaces[3].replace(/ /g,'_')
ut = RegExp ('\\[\\[' + ut.toLowerCase() + ':[^#]+$', 'i')
txt = txt.replace(/\[\[[^\]\|\n]+/g, function(lnk){
return ut.test(lnk) ? lnk : decodeAnchor(lnk)
})
}
return txt
}
 
//replace text
tbox.textSelection( 'encapsulateSelection', {replace:true, peri:newText} )
 
//end of main code
function simplifyMatched(str, bracket, url, rest){//arguments: (whole string), '[', url, ' name]'; calls decodeUrl
return
if (!bracket){//no brackets, just url
 
var trail = url.match(RegExp('[,;\\\\\.:!\\?' //trailing punctuation, per Parser.php
 
+ (!/\(/.test(url) ? '\\)' : '') + ']+$' )) //trailing no-matching )
 
if (trail) url = url.substring(0, url.length-trail[0].length) //move these out of url
 
 
//---FUNCTIONS
 
 
function simplifyMatched(str, bracket, url, rest){//arguments: (whole string), '[', url, ' name]'
 
if( !bracket ){//no brackets, just url
var trail = RegExp(
'['
+ ',;\\\\\.:!\\?' //trailing punctuation, per Parser.php
+ ( /\(/.test(url) ? '' : '\\)' ) //also closing bracket without opening bracket
+ ']+$'
+ "|''+$" //or possible bold/italic at the end of url
)
.exec( url )
if( trail ){
url = url.substring( 0, url.length - trail[0].length ) //move these out of url
}
return decodeUrl(url) + str.substring(url.length)
 
}else if (rest) //both brackets and possibly name
}else if( rest ){ //both brackets and possibly name
return decodeUrl(url, rest.replace(/\]+$|^ +| +$/g,'')) //trim ending brackets and spaces in 'name]'
 
else return str //probably broken wikicode in selected text
}else{
return str //probably broken wikicode in selected text
}
}
 
 
 
function decodeUrl(url, name){ //url -> %-decoded -> [[link|name]] (if possible); name is optional
 
var decodingFailed //need to skip some strange percent-encoded URIs
url = unSecure(url)
 
if (url.indexOf('%') != -1) try { url = decodeURI(url) } catch(e){} //decode %
//percent-decoding
url = url.replace(/%(3B|2F|2C|3A)/g, decodeURIComponent) //decode ;/,:
if( url.indexOf('%') != -1 )
url = url.replace(/[ <>"\[\]]/g, encodeURIComponent) //" disallowed chars
try {
if (isBeforeCursor)
url = decodeURI(url)
for (var n in window.urlDecoderEngNames) //to eng keywords
url = url.replace(/%(3B|2F|2C|3A)/g, decodeURIComponent) //decode ;/,:
url = url.replace(/[ <>"\[\]|]/g, encodeURIComponent) //" some disallowed chars, and pipe can screw template params
} catch(e){
decodingFailed = true
}
 
if( isBeforeCursor ) //user-defined conversion to eng keywords
for( var n in window.urlDecoderEngNames )
url = url.replace(RegExp('(title=|wiki\/)('+urlDecoderEngNames[n]+':)'), '$1' + n + ':')
 
var link
//try converting to internal link
if (!/(\}\}|\|)$/.test(url)) link = toWikilink(url) //trailing | or }} can be a part of template, skip to be safe
if( !decodingFailed && !/(\}\}|\|)$/.test(url) ) //trailing | or }} could mean a part of a template, skip to be safe
if (!link && window.urlDecoderCustom
var && (link = urlDecoderCustomtoWikilink(url)) && /^(https?:\/\/|\{\{)/.test(link))
 
{url = link; link = null} //still external
//user-defined function
if (link){
if( window.urlDecoderCustom ){
link = link.replace(/%(3f|26|22)/ig, decodeURIComponent) //decode ?&"
url = urlDecoderCustom(url)
if ((wgNamespaceNumber==0 || wgNamespaceNumber==14)
if( ! /^(https?:\/\/|\{\{)/.test(url) ) link = url //was converted to internal link
&& isBeforeCursor)
link=link.replace(/^:/,'') //probably interwiki
return '[\[' + link + (name?'|'+name:'') + ']]'
}
 
if (isBeforeCursor || typeof name == 'string')
//return internal link
url = url.replace(/''/g,'%27%27')//techically '' means the end of URL, but more likely it's part of it
if( link ){
if (typeof name == 'string') return '[' + url + (name?' '+name:'') + ']' //empty name
link = link.replace(/%(3f|26|22)/ig, decodeURIComponent) //decode ?&"
else return url
if( (wgNamespaceNumber==0 || wgNamespaceNumber==14) && isBeforeCursor )
link = link.replace(/^:/,'') //probably user adding interwiki
return '[\[' + link + (name?'|'+name:'') + ']]'
}
 
//or return external link
if( typeof name == 'string' ){
if( isBeforeCursor ) url = url.replace(/''/g,'%27%27') //techically '' should stop URL, but more likely it's part of it
return '[' + url + (name?' '+name:'') + ']' //empty name
}else{
return url
}
 
}
 
 
function toWikilink(url){//url -> wikilink, otherwise null
 
//try bugzilla and user-defined prefixes
function toWikilink(url){ // 'http://xx.wikipedia.org/wiki/YY' -> xx:YY
if (!window.urlDecoderPrefixes) urlDecoderPrefixes = {}
 
urlDecoderPrefixes['https://bugzilla.wikimedia.org/show_bug.cgi?id=']='mediazilla'
//add bugzilla to user-defined prefixes
for (var key in urlDecoderPrefixes)
urlDecoderPrefixes = $.extend( window.urlDecoderPrefixes,
if (url.toLowerCase().indexOf(key)!=-1)
{ 'https://bugzilla.wikimedia.org/show_bug.cgi?id=' : 'mediazilla' } )
return urlDecoderPrefixes[key]+':'+ url.substring(url.indexOf(key)+key.length)
 
//try WM prefixes
//apply user-defined prefixes
var parts = url.substring(7).split('/')
for( var key in urlDecoderPrefixes )
if (parts[1]!='wiki' || url.indexOf('?')!=-1) return null
var linkPrefix = WMPrefixesif( url.toLowerCase().indexOf(key), prefixes != ''-1 )
return urlDecoderPrefixes[key] + ':' + url.substring( url.indexOf(key) + key.length )
if (!linkPrefix) return null
 
var title = url.substring(parts[0].length + parts[1].length + 9) //get part after /wiki/
//check if we can convert to internal link with WM prefixes
title = decodeAnchor(title)
var ma = /^(http:\/\/[^\/]+)\/wiki\/([^?]+)$/.exec( url )// 1:'http://___domain.org' 2:part after /wiki/
if (linkPrefix[0] && (linkPrefix[0] != localPrefix[0])) prefixes = linkPrefix[0]
if( !ma ) return null
if (linkPrefix[1] && (linkPrefix[1] != localPrefix[1])) prefixes += ':' + linkPrefix[1]
var linkPrefix = WMPrefixes( ma[1] )
if (prefixes || isColonNeeded(title)) prefixes += ':' //dividing colon or cat/file leading colon
if( !linkPrefix) return null
 
//convert to internal
var title = decodeAnchor( ma[2] )
var prefixes = ''
if( linkPrefix[0] && (linkPrefix[0] != localPrefix[0]) ) prefixes = linkPrefix[0]
if( linkPrefix[1] && (linkPrefix[1] != localPrefix[1]) ) prefixes += ':' + linkPrefix[1]
if( prefixes || isColonNeeded(title) ) prefixes += ':' //colon after prefix or leading colon on cat/file link
return prefixes + title
 
}
 
 
 
 
Line 173 ⟶ 254:
link = link.replace(/(_|%20)/g, ' ').replace(/^ +| +$/g, '')
var parts = link.split('#')
if ( parts.length != 2 ) return link //no anchor
var anchor = parts[1], hidIdx = -1, hidden = []
//decode 4, 3 and 2-byte: http://en.wikipedia.org/wiki/UTF-8
Line 186 ⟶ 267:
anchor = anchor.replace(/\.[2-7][0-9A-F]/g, function(hhh){
var ch = deChar(hhh)
if ( '!"#$%&\'()*+,/;<=>?@\\^`~'.indexOf(ch) >= 0 ) return ch;
else return hhh
})
//unhide IPs and return
for ( var i=hidIdx; i>=0; i-- ) anchor = anchor.replace('\x01'+i+'\x02', hidden[i])
if ( anchor.indexOf("''") != -1 ) return link //cannot have double '' in link
else return parts[0] + '#' + anchor
 
function deChar(ss){
try{ss = decodeURIComponent(ss.replace(/\.([0-9A-F][0-9A-F])/g, '%$1'))} catch(e){}
return ss
}
}
 
 
function WMPrefixes(url){ // htp://en.wikipedia.org/wiki/... -> [ 'w', 'en']
 
var dd = url.substring(7).split('/')[0].split('.') // -> ['en','wikipedia','org']
 
if (dd.pop() != 'org') return null
 
var proj='', lang = '', part = dd.pop()
function WMPrefixes(url){ // http: //en.wikipedia.org/wiki/... -> [ 'w', 'en']
if (proj = {'mediawiki':'mw','wikimediafoundation':'foundation'}[part]);
 
else if (proj = {'wikipedia':'w','wikibooks':'b','wikinews':'n','wikiquote':'q',
var dd = /^http:\/\/([a-z\.]+)\.org/.exec( url.toLowerCase() )
'wikisource':'s','wikiversity':'v','wiktionary':'wikt'}[part]){
if( !dd ) return null
lang = dd.pop()
dd = dd[1].split('.') //domains, e.g. ['en','wikipedia']
if (!lang || lang=='www') lang = ''
if( dd.length > 2 ) return null //too many subdomains, possibly mobile site XX.m.wikipedia.org/
else if (lang=='test') {lang=''; proj='testwiki'}
 
}else if (part == 'wikimedia'){
var lang = part'', proj = '', ___domain = dd.pop(), subdomain = dd.pop()
if (!part ||subdomain part== 'www' ) projsubdomain = 'foundation'
 
else if (/^(meta|commons|incubator|species|strategy)$/.test(part)) proj = part
if( ___domain == 'wikimedia' ){ // *.wikimedia.org
else return null
if( !subdomain )
}else return null
proj = 'foundation'
else if( wmSubDomains.test(subdomain) )
proj = subdomain
else
return null
 
}else if( (proj = wmDomain[___domain]) && !subdomain ){ // mediawiki.org & wikimediafoundation.org
//done: proj is set
 
}else if( proj = wmDomainM[___domain] ){ //multi-lang domains
if( !subdomain );
//done: e.g. 'wikisource.org'
else if( proj == 'w' && subdomain == 'test' )
proj = 'testwiki'
else if( subdomain.length >= 2 )
lang = subdomain
else
return null
 
}else return null //unrecognized ___domain
 
return [proj, lang]
 
}
 
 
 
function unSecure(url){
return url.replace(/https:\/\/secure\.wikimedia\.org\/(\w+)\/(\w+)\/([^\]\|\n\r ]+)/,
var mm = /https:\/\/secure\.wikimedia\.org\/(\w+)\/(\w+)\/([^\]\|\n\r ]+)/i.exec( url )
'http://$2.$1.org/$3')
if( !mm) return url
}
var ___domain = mm[1].toLowerCase(), sub = mm[2].toLowerCase()
 
if( ! wmDomainM[___domain] ) return url //___domain not recognized
function isColonNeeded(pg){
if (pg.indexOf(':')==-1) return false
if( ___domain == 'wikipedia' ) //handle some special cases
else return RegExp('^('+getDecoderNS()+'|file|category) *:','i').test(pg)
switch( sub ){
case 'mediawiki': sub = 'www'; ___domain = 'mediawiki'; break
case 'foundation': sub = ''; ___domain = 'wikimediafoundation'; break
case 'sources': sub = ''; ___domain = 'wikisource'; break
default:
if( wmSubDomains.test(sub) ) ___domain = 'wikimedia' // .../wikipedia/meta -> meta.wikimedia.org
//otherwise: consider it language: .../wikipedia/en
}
return 'http://' + (sub ? sub + '.' : '') + ___domain + '.org/' + mm[3]
}
 
function getDecoderNSisColonNeeded(pg){
if( ! /:/.test(pg) ) return false
if (!window.urlDecoderNS) var urlDecoderNS = {}
if( ! colonNS ){ //define list of all possible category and file namespaces
urlDecoderNS.en = 'image'
var list = ['file', 'category'] //canonical aliases
var ns = urlDecoderNS[wgContentLanguage]
//if( !window.wgNamespaceIds ) return alert('Warning: wgNamespaceIds not defined, old MediaWiki version?');
if (typeof ns == 'string') return ns //user-defined list
if for(!window. var name in wgNamespaceIds) {)
alert if('Warning: (wgNamespaceIds[name]==6 not|| defined,wgNamespaceIds[name]==14) old&& MediaWiki$.inArray(name, version?'list); return== ''-1 )
list.push(name)
colonNS = RegExp( '^(' + list.join('|') + ') *:', 'i')
}
return colonNS.test( $.trim(pg) )
ns = ''
for (var name in wgNamespaceIds)
if (wgNamespaceIds[name]==6 || wgNamespaceIds[name]==14)
ns += '|' + name
ns = ns.substring(1)
urlDecoderNS[wgContentLanguage] = ns
return ns
}
 
}