Участник:Js/linkcomplete.js
Перейти к навигации
Перейти к поиску
Страница персонального оформления. У этого JS-кода есть документация: Участник:Js/linkcomplete.
После сохранения очистите кэш браузера.
После сохранения очистите кэш браузера.
function linkComplete(){//global wrapper
var suggestState, useOpenSearch = false
var typedTitle, typedTitleOrig, suggestNS, suggestNamespace, suggestSuffix
var suggestLimit = 20, suggestURL, queryCont, typingTimeoutId = 0, suggestRedirects, displayAllNS
var results, resultsIdx, sel
var txtBC //text before cursor
var aj, ajPath = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php?action=', ajTimeoutId, ajCache = {}, nsNames
var cycleNS = [14, 2, 6, 4]
//Initialization
if (window.opera && opera.version() < '9.5') return
txtBox = document.getElementById('wpTextbox1')
addHandler(txtBox, window.opera?'keypress':'keydown', textOnKeyPress)
addToolbarButton('[\[→]]', suggestStart, '', 'Suggest link [ctrl-↓]', window.es_accesskey || '')
//intercept InsertTags
/*
window.insertTags_Old = insertTags
insertTags = function (op, cl, sample){
insertTags_Old(op, cl, sample)
if (/\[\[/.test(op)) { sel.put(''); suggestStart() }
}
*/
return
function delayedInit(){
appendCSS('\
table#lc_results {border:1px solid gray; padding:1px; background:#F5F5F5; cursor:pointer}\
table#lc_results th {background:#EAEAFF; white-space:nowrap}\
table#lc_results th div {float:right; margin-left:1em; border:1px outset gray; font-weight:normal}\
tr.ac_selected {background:gray}\
textarea.suggesting {' + (window.opera?'border:2px inset #EEEEEE;':'') + 'border-left-color:orange}')
addHandler(txtBox, 'click', function(){suggestOff()})
//define local NS
switch (mw.config.get('wgDBname')){
case 'ruwiki':
nsNames = {
'-2':'Медиа', '-1':'Служебная', 1:'Обсуждение',
2:'Участник|Участница', 3:'Обсуждение участника|обсуждение участницы',
4:'Википедия|ВП',5:'Обсуждение Википедии',
6:'Изображение', 7:'Обсуждение изображения',
9:'Обсуждение MediaWiki',
10:'Шаблон', 11:'Обсуждение шаблона',
14:'Категория', 15:'Обсуждение категории',
100:'Портал', 101:'Обсуждение портала'
}
break
case 'enwiki':
nsNames = { 4:'Wikipedia|WP', 5:'Wikipedia talk|WT', 100:'Portal', 101:'Portal talk' }
break
default: //API request?
}
//add canonical NS
var can='Media|Special||Talk|User|*|Project|*|Image|*|MediaWiki|*|Template|*|Help|*|Category|*'.split('|')
for (var i=0; i<can.length; i++)
nsNames[i-2] = (nsNames[i-2] ? nsNames[i-2]+'|' : '') + (can[i]=='*' ? can[i-1]+' talk' : can[i])
//initialize selection
sel = selectionTools
sel.ini(txtBox)
}
// *** EVENTS ***
function textOnKeyPress(e){
e = e || window.event
var key = e.keyCode
ajCancel()
if (e.ctrlKey && (key==38||key==40)){ //up or dn
eventStop(e)
suggestStart(key==38) //opensearch or not
return
}
if (!suggestState) return
//while suggesting
//escape always stops the script
if (key==27){ eventStop(e); suggestCancel(); return }
//if user already started to type something: simply restart the timeout, waiting for him to stop
if (typingTimeoutId){ typingTimeout(true); return }
//in the suggestion mode
switch (key){
case 37:case 8:case 46: eventStop(e); suggestCancel(); return //left, bs, delete
case 38: eventStop(e); suggestNext(-1); return //up
case 40: eventStop(e); suggestNext(+1); return //down
case 32: if (e.ctrlKey) { eventStop(e); suggestStart(!useOpenSearch); return }; break
case 39: //right
eventStop(e)
if (typedTitle) suggestAccept()
else suggestNamespaces(true) //show all namespaces
return
case 13: eventStop(e); suggestAccept(true); return //Enter
//maybe also check e.shiftKey || e.ctrlKey ?
case 17: case 16: return //Ctrl or Shift: do nothing
//case 32: eventStop(e); suggestStart(); return //space
}
//for all other keys
typingTimeout(true)
}
function typingTimeout(isStart){
if (isStart){
popupHide()
typingTimeout(false)
typingTimeoutId = setTimeout(suggestContinue, 500)
}else{
if (typingTimeoutId) clearTimeout(typingTimeoutId)
typingTimeoutId = 0
}
}
function suggestOn(){
suggestNS = 0; suggestSuffix = ''; suggestNamespace = ''; suggestRedirects = false
typingTimeoutId = 0
if (!suggestState) txtBox.className += ' suggesting'
suggestState = true
}
function suggestOff(msg, period){
if (!suggestState) return
suggestState = false
ajCancel()
txtBox.className = txtBox.className.replace(/ ?suggesting/, '')
popupHide()
sel.put('')
if (msg) msgShow(msg, period || 2000)
}
function suggestCancel(){
typingTimeout(false) //just in case
replaceTitle(typedTitleOrig, true) //replace with old if we changed lettercase
suggestOff()
}
function rememberCursorPosition(){
txtBC = sel.getCursorText(true) //get text before cursor
}
function suggestStart(isOpenSearch){
//initialize
if (!nsNames) delayedInit()
if (typeof isOpenSearch == 'boolean') useOpenSearch = isOpenSearch
if (!suggestState){//just started
if (sel.get()) return //do nothing if text selected?
rememberCursorPosition() //into txtBC
}
suggestOn()
//determine what's before cursor
var ma, res, line = txtBC.substring(txtBC.lastIndexOf('\n'))
if (ma=line.match(/\[\[:?([^\]\[\|]*)$/)){ //[[words
res = parseNS(ma[1])
suggestNS = res[0]
typedNamespace = res[1] ? res[1]+':' : ''
typedTitle = res[2]
//check for already existing ending brackets
line = sel.getCursorText(false)
if (/\n/.test(line)) line = line.substring(0, line.indexOf('\n')) //cur line after cursor
ma = line.match(/\|?\]\]/) //the rest of the line
if (ma) suggestSuffix = ma[0]
//else if (ma=line.match(/\{\{([^\}\]\|]+)$/)) //template
// typedTitle = ma[1]
// suggestNS = 10
}else if (ma=line.match(/[^\s\[\]\|\{\}"]+$/)){ //word before cursor
typedTitle = ma[0]
sel.move(-typedTitle.length)
sel.put('[[', false)
sel.move(typedTitle.length)
rememberCursorPosition()
}else{ //start a new link
sel.put('[[', false)
typedTitle = typedNamespace = ''
rememberCursorPosition()
}
//normalize and go
typedTitleOrig = typedTitle
replaceTitle(normalize(typedTitle))
sel.save()
if (typedTitle) titlesRequest()
else suggestNamespaces()
}
function suggestContinue(){ //called on timeout, checks if selection was changed to cancel continuos suggestion
typingTimeoutId = 0
var txtBCOld = txtBC
rememberCursorPosition()
//check that cursor moved to the right but stayed on the same line
if ((txtBC.length <= txtBCOld.length)
|| (txtBC.substring (0, txtBCOld.length) != txtBCOld)
|| (/\n/.test(txtBC.substring(txtBCOld.length))))
suggestOff()
else //restart suggestions
suggestStart()
}
function suggestNamespaces(isAll){ //start suggesting namespaces
function getNS(nn){ var ns = nsNames[nn].split('|')[0]; return ns ? ns+':' : '' }
results = []
//create array
if (isAll){
for (var i=-2; i<110; i++)
if (typeof nsNames[i] == 'string') results.push(getNS(i))
}else{
results.push('')
for (var i=0; i<cycleNS.length; i++) results.push(getNS(cycleNS[i]))
}
displayAllNS = isAll
//try to find already type namespace
resultsIdx = -1
for (var i=0; i<results.length; i++) if (results[i] == typedNamespace) {resultsIdx = i; break }
popupShow()
popupUpdate()
}
// *** API ***
function titlesRequest(){
if (typedTitle=='z') {//local testing
results = ['Z', 'Zebra', 'Zombie', 'Zaraza']
titlesShow()
return
}
var ns = suggestNS, pr = typedTitle, url
if (ns<0){ //allpages won't work with negative namespaces
useOpenSearch = true; ns = ''; pr = typedNamespace + pr
}
msgShow(pr + ' ...' + (suggestRedirects ? ' (redirects)':''))
if (useOpenSearch) url = 'opensearch&search=<pr>&limit=<li>&namespace=<ns>'
else url ='query&format=json&list=allpages&apprefix=<pr>&aplimit=<li>&apnamespace=<ns>&apfilterredir=<re>'
url = url.replace('<li>', suggestLimit)
url = url.replace('<ns>', ns)
url = url.replace('<re>', suggestRedirects ? 'redirects' : 'nonredirects')
url = url.replace('<pr>', encodeURIComponent(pr))
ajCall(url, titlesReceive, 5000)
}
function titlesReceive(query){
zq = query
//analyze result
var q = query, err = ''
if (!q) err='API: unrecognized error'
else if (q==-1) err='API: bad response'
else if (q==-2) err='API: timeout'
else if (q.error) err='API response: ' + q.error.code + ':' + q.error.info
if (err) {suggestOff(err); return }
//convert to array
if (q.length){//opensearch
results = new Array(q[1].length)
for (var i=0; i<q[1].length; i++) results[i] = q[1][i]
queryCont = false
}else{//allpages
if (!(q=q.query)) err='API: empty response'
else if (!(q=getAnyChild(q))) err='API: empty results'
if (err) {suggestOff(err); return }
//convert titles into array
results = new Array(q.length)
for (var ttl, i=0; i<q.length; i++) results[i] = q[i].title || q[i].name || q[i]['*']
queryCont = getAnyChild(getAnyChild(query['query-continue']))
}
//separate namespace from titles
if (suggestNS && results.length>0){
suggestNamespace = results[0].substring(0, results[0].indexOf(':')) //get correct namespace name
for (var i=0; i<results.length; i++) results[i] = results[i].substring(suggestNamespace.length+1)
}
//check for ending space, because opensearch also returns a title w/o space
if (/ $/.test(typedTitle)){
for (var i=0; i<results.length; i++)//remove all imcompatible titles
if (typedTitle.toLowerCase() != results[i].substring(0, typedTitle.length).toLowerCase())
results.splice(i, 1)
}
titlesShow()
}
// *** INTERACTION ***
function titlesShow(){ //display result
msgHide()
resultsIdx = 0
if (results.length == 0) return suggestOff('x x x')
else if (results.length == 1) msgShow(results[0], 2000)
else if (results[0]==uppercase(typedTitle)) resultsIdx = 1 //several results: skip "the same as typed text"
popupShow()
suggestNext()
}
function suggestNext(step){
//if (!sel.compare()) { suggestContinue(); return }
//increment
if (step) resultsIdx += step
if (resultsIdx < 0) resultsIdx = results.length-1
else if (resultsIdx > results.length-1) resultsIdx = 0
// !!! temp?
sel.put('')
//suggest next ...
if (typedTitle) replaceTitle(results[resultsIdx])
else replaceNamespace(results[resultsIdx])
sel.save()
popupUpdate()
}
function replaceTitle(newTitle, isForce){ //2nd arg: always replace 1st letter
sel.put('') //remove selected part of suggestion
//replace typed part if needed: opensearch is case-insensitive
var newTyped = newTitle.substring(0, typedTitle.length) //new "typed text"
if (((isForce || suggestNS) ? typedTitle : uppercase(typedTitle)) != newTyped) {
sel.move(-typedTitle.length, 0)
typedTitle = newTyped
sel.put(typedTitle, false)
}
//suggest the remaining part
var newSuggested = newTitle.substring(typedTitle.length)
if (newSuggested) sel.put(newSuggested) //insert new
}
function replaceNamespace(newName){
sel.move(-typedNamespace.length, 0) //select old suggestion
typedNamespace = newName
sel.put(typedNamespace, false)
rememberCursorPosition()
}
function suggestAccept(needPipe){
//if (cursorPosChanged()) return
if (!typedTitle) return //accepted namespace - nothing else to do
if (suggestNS==0 && needPipe) replaceTitle(results[resultsIdx], true) //make sure target is upercase
sel.collapse() //accept selected part
suggestOff()
if (suggestSuffix) return
if (suggestNS==14) sel.put('\n', true) //line break after category
if (needPipe){ //insert pipe so user can type link name
sel.put('|', false)
sel.put(']]', true)
if (suggestNS==14 && mw.config.get('wgNamespaceNumber')!=0) sel.put('{\{PAGENAME}}')
}else{
sel.put(']]')
sel.collapse(false)
}
}
function cursorPosChanged(){
//if (sel.compare()) return false
var msg = 'text selection changed'
if (window.opera && opera.version < '9.50') msg += ', Opera < 9.50 is incompatible, please upgrade'
suggestOff(msg, 2000)
return true
}
// *** POPUP ***
var popupDiv, popupTable
function popupShow(){ //create and show results table
//if (results.length < 2) { popupTable = null; return } //no popup if there is only one result
if (!popupDiv){//create
popupDiv = document.createElement('div')
document.body.appendChild(popupDiv)
addHandler(popupDiv, 'mouseup', popupOnClick) //mouseup is to detect middle clicks as well
}
var hdrText = '', hdrStyle = ''
if (!typedTitle){
if (!displayAllNS) hdrText += ' <div>[→]</div>'
hdrText += '___ :'
}else{
if (useOpenSearch){
hdrStyle = 'font-style:italic'
hdrText += ' <div>opensearch</div>'
}else{
hdrText += '<div>pages</div>'
}
hdrText += suggestNamespace + ':'
if (queryCont) hdrText += '<span style="font-weight:notmal"> ...</span>'
}
var html = '<table id="lc_results"><tr><th style="text-align:left;'
+ hdrStyle + '">' + hdrText + '</th></tr>'
for (i=0; i<results.length; i++)
html += '<tr><td style="cursor:pointer" id="ac_result_'+i+'">'+results[i]+' </td></tr>'
//if (queryCont) html += '<tr><td id="ac_next" style="text-align:center; line-height:0.2em">...</td></tr>'
popupDiv.innerHTML = html + '</table>'
popupTable = popupDiv.firstChild
popupTable.style.cssText = 'position:absolute; right:3px; z-index:100; width:auto; display:none' //opacity:0.8;
if (suggestRedirects) popupTable.style.fontStyle = 'italic'
}
function popupHide(){ if (popupTable) popupTable.style.display = 'none' }
function popupOnClick(e){
//setTimeout(sel.focus, 100)
txtBox.focus()
e = e || window.event
var targ = e.target || e.srcElement
if (targ.nodeName=='DIV'){
if (!typedTitle) suggestNamespaces(true)
else suggestStart(!useOpenSearch)
return
}else if (targ.nodeName=='TH'){
//txtBox.focus()
}
//find row number
var tr = targ.parentNode, trs = tr.parentNode.getElementsByTagName('tr'), i
for (i=1; i<trs.length; i++) if (trs[i]==tr) break //start with 1 to skip header row
if (i >= trs.length) return popupHide() //not found for some reason
resultsIdx = i - 1
//shift or middle click - open window
var isIE = navigator.userAgent.indexOf('MSIE') != -1
if (typedTitle && (e.shiftKey || (isIE && e.button == 4) || (!isIE && e.button == 1))){
window.open (mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace('\$1', suggestNamespace+':'+results[resultsIdx]))
return
}
//otherwise - accept
suggestNext()
suggestAccept(true)
}
function popupUpdate(){
if (!popupTable) return
popupTable.style.top = windowScrolled() + 'px'
popupTable.style.display = ''
var trs = popupTable.getElementsByTagName('tr')
if (popupTable.lastMarked) trs[popupTable.lastMarked].className = '' //unmark previous
if (resultsIdx == -1) return
trs[resultsIdx+1].className = 'ac_selected'
popupTable.lastMarked = resultsIdx + 1
}
// *** MESSAGE ***
var msgDiv, msgTimeoutId
function msgShow(msg, period){
if (!msgDiv){ //create
msgDiv = document.createElement('div')
msgDiv.style.cssText = 'position:absolute; z-index:100; background:#FFE7A4; color:black; border:1px solid gray; padding:2px'
document.body.appendChild(msgDiv)
}
if (msgTimeoutId) { clearTimeout(msgTimeoutId); msgTimeoutId = 0 }
//position
var pos = getElemPos(txtBox)
msgDiv.style.left = (pos[0] + 2)+ 'px'
msgDiv.style.top = (Math.max(pos[1], windowScrolled()) + 2) + 'px'
//show
msgDiv.style.display = ''
msgDiv.innerHTML = msg
msgTimeoutId = setTimeout(msgHide, period || 5000)
}
function msgHide(){ if (msgDiv) msgDiv.style.display = 'none' }
// *** AAJAX ***
function ajCall(params, func, timeout){
if (ajCache[params]) { func(ajCache[params]); return }
if (aj) ajCancel()
//if (!aj){
aj = sajax_init_object()
aj.onreadystatechange = ajOnReady
//}else ajCancel()
ajParams = params
aj.open('GET', ajPath + params, true)
ajTimeoutId = setTimeout(ajOnTimeout, timeout || 10000)
aj.send(null)
function ajOnReady(){
if (aj.readyState != 4) return
clearTimeout(ajTimeoutId)
if (aj.status != 200) return func(-1)
var q
try {
eval('q='+aj.responseText)
ajCache[ajParams] = q
}catch(e){}
func(q)
}
function ajOnTimeout(){
ajCancel()
func(-2)
}
}
function ajCancel(){
if (aj && (aj.readyState==2 || aj.readyState==3)){
clearTimeout(ajTimeoutId)
aj.abort()
}
}
function getAnyChildKey(obj) { for(var key in obj) return key; return null }
function getAnyChild(obj) { var key = getAnyChildKey(obj); return key ? obj[key] : null }
// *** MISC ***
function normalize(tt){ return tt.replace(/[_ ]+/g,' ').replace(/^ /, '') } //except uppercase and ending spaces
function uppercase(tt){ return tt.substring(0,1).toUpperCase() + tt.substring(1) }
function parseNS(pgname){ // 'project:dd' -> [4, 'project', 'd']
var pos = pgname.indexOf(':'), nsn, nsname
if (pos != -1){
nsname = pgname.substring(0, pos)
for (var i in nsNames)
if (('|'+nsNames[i].toLowerCase()+'|').indexOf('|'+nsname.toLowerCase()+'|') != -1)
{nsn = i; break}
}
if (nsn) pgname = pgname.substring(pos+1)
else { nsn = 0; nsname = '' }
return [nsn, nsname, pgname]
}
function addToolbarButton(name, onclick, id, tooltip, accesskey){
var toolbar = document.getElementById('toolbar')
if (!toolbar) return
var newBtn = document.createElement('input')
newBtn.type = 'button'; newBtn.style.cssText = 'background:#adbede; height:22px; vertical-align:middle; padding:0'
if (name) newBtn.value = name
if (onclick) newBtn.onclick = onclick
if (id) newBtn.id = id
if (tooltip) newBtn.title = tooltip
if (accesskey) newBtn.accessKey = accesskey
toolbar.appendChild(newBtn)
return newBtn
}
// *** DOM function ***
function getElemPos(elem){
var x = 0, y = 0
while (elem){ x += elem.offsetLeft; y += elem.offsetTop; elem = elem.offsetParent }
if (navigator.userAgent.indexOf('Mac') != -1 && typeof document.body.leftMargin != 'undefined'){
x += document.body.leftMargin
y += document.body.topMargin
}
return [x, y]
}
function windowScrolled(){
if (self.pageYOffset) // all except Explorer
return self.pageYOffset
else if (document.documentElement && document.documentElement.scrollTop) // Explorer 6 Strict
return document.documentElement.scrollTop
else if (document.body) // all other Explorers
return document.body.scrollTop
}
function eventStop(ev){
if (ev.preventDefault) ev.preventDefault(); else ev.returnValue = false
}
}//linkComplete
if (mw.config.get('wgAction')=='edit' || mw.config.get('wgAction')=='submit')
$(linkComplete)
// *** TEXT SELECTION ***
var selectionTools = new function(){
var tBox
this.ini = function(el){ tBox = el } // !!! maybe check for really old browsers and quit?
this.get = function (){
this.focus()
if (document.selection)//IE/Opera
return document.selection.createRange().text
else if (tBox.selectionStart || tBox.selectionStart == '0')// Mozilla
return tBox.value.substring(tBox.selectionStart, tBox.selectionEnd)
else
return null
}
this.put = function(txt, isCollapse){ //isCollapse: true: set cursor at start, false: at the end of selection
this.focus()
if (tBox.selectionStart || tBox.selectionStart == '0'){// Mozilla/Opera
var p1 = tBox.selectionStart, p2 = tBox.value.length - tBox.selectionEnd
var s = tBox.scrollTop
tBox.value = tBox.value.substring(0, p1) + txt
+ tBox.value.substring(tBox.selectionEnd, tBox.value.length)
tBox.scrollTop = s
tBox.selectionStart = p1
tBox.selectionEnd = tBox.value.length - p2
}else if (document.selection){//IE
r = document.selection.createRange()
r.text = txt
r.select() //IE need this to make sure cursor is after selection
this.move(-txt.length, 0)
}
if (typeof isCollapse == 'boolean') this.collapse(isCollapse)
}
this.collapse = function(isStart){ //true: collapse to selection start
if (tBox.selectionStart || tBox.selectionStart == '0'){// Mozilla
if (isStart) tBox.selectionEnd = tBox.selectionStart
else tBox.selectionStart = tBox.selectionEnd
}else if (document.selection){//IE
var r = document.selection.createRange()
r.collapse(isStart ? true : false)
r.select()
}
}
this.move = function(mv1, mv2){
if (typeof mv2 == 'undefined'){//move cursor, i.e.empty selection
if (mv1>0) this.move(mv1, 0); else this.move(0, mv1)
}else if (tBox.selectionStart || tBox.selectionStart == '0'){
var end = tBox.selectionEnd + mv2
tBox.selectionStart += mv1
tBox.selectionEnd = end
}else if (document.selection){
var r = document.selection.createRange()
r.moveStart('character', mv1)
r.moveEnd('character', mv2)
r.select()
}
}
this.pos = function(){ //returns the position of selection end
this.focus()
if (tBox.selectionStart || tBox.selectionStart=='0')
return tBox.selectionEnd
else if (document.selection){
var r = document.selection.createRange()
r.moveToElementText(tBox)
r.setEndPoint('StartToEnd', document.selection.createRange())
return tBox.value.length - r.text.replace(/n/g, '\n\r').length
}
}
// flaw in IE? "text |" and "text\n|" will return the same cursor position, be
//returns the whole text before or after the cursor; we assume that the selection is empty
this.getCursorText = function(isBefore){
if (tBox.selectionStart || tBox.selectionStart=='0'){
if (isBefore) return tBox.value.substring(0, tBox.selectionStart)
else return tBox.value.substring(tBox.selectionEnd, tBox.value.length)
}else if (document.selection){
var s = document.selection.createRange(), r = s.duplicate()
r.moveToElementText(tBox)
if (isBefore) r.setEndPoint('EndToStart', s)
else r.setEndPoint('StartToEnd', s)
var txt = r.text, old = s.text.length, cont = true
do { s.moveStart('character', -1)} while ((s.text.length==old) && (txt+='\n')) //add missing \n
return txt
}
}
this.focus = function(){ tBox.focus() }
this.save = function(){
if (tBox.selectionStart || tBox.selectionStart == '0'){
tBox.selStart = tBox.selectionStart; tBox.selEnd = tBox.selectionEnd
}else if (document.selection)
tBox.selRange = document.selection.createRange()
}
this.restore = function(){
if (tBox.selRange) tBox.selRange.select()
else this.move(tBox.selStart-tBox.selectionStart, tBox.selEnd-tBox.selectionEnd)
}
this.compare = function(){
if (tBox.selRange) return document.selection.createRange().isEqual(tBox.selRange)
else return (tBox.selStart==tBox.selectionStart && tBox.selEnd==tBox.selectionEnd)
}
}