User:Yair rand/interwikiwatchlist.js

From Wikipedia, the free encyclopedia
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.
// newNode from [[wikt:Mediawiki:Common.js]], JsMwApi from [[wikt:WT:EDIT]], 
// interwiki watchlist from [[wikt:User:Yair rand/superwatchlist.js]]. To enable, add 
// importScript ("User:Yair rand/interwikiwatchlist.js"); 
// to [[Special:MyPage/common.js]]. You should see an "Import watchlist" button 
// in the top-right corner of Special:Watchlist. For more info see the notes at the 
// top of http://en.wikipedia.org/wiki/User_talk:Yair_rand/interwikiwatchlist.js

function newNode(tagname){
 
  var node = document.createElement(tagname);
 
  for( var i=1;i<arguments.length;i++ ){
 
    if(typeof arguments[i] == 'string'){ //Text
      node.appendChild( document.createTextNode(arguments[i]) );
 
    }else if(typeof arguments[i] == 'object'){ 
 
      if(arguments[i].nodeName){ //If it is a DOM Node
        node.appendChild(arguments[i]);
 
      }else{ //Attributes (hopefully)
        for(var j in arguments[i]){
          if(j == 'class'){ //Classname different because...
            node.className = arguments[i][j];
 
          }else if(j == 'style'){ //Style is special
            node.style.cssText = arguments[i][j];
 
          }else if(typeof arguments[i][j] == 'function'){ //Basic event handlers
            try{ node.addEventListener(j,arguments[i][j],false); //W3C
            }catch(e){try{ node.attachEvent('on'+j,arguments[i][j],"Language"); //MSIE
            }catch(e){ node['on'+j]=arguments[i][j]; }}; //Legacy
 
          }else{
            node.setAttribute(j,arguments[i][j]); //Normal attributes
 
          }
        }
      }
    }
  }
 
  return node;
}

//JsMwApi documentation is at http://en.wiktionary.org/wiki/User_talk:Conrad.Irwin/Api.js
function JsMwApi (api_url, request_type) {
 
	if (!api_url) 
	{
		if (typeof(true) === 'undefined' || true == false)
			throw "Local API is not usable.";
 
		api_url = mw.config.get('wgScriptPath') + "/api.php";
	}
 
	if (!request_type)
	{
		if (api_url.indexOf('http://') == 0 || api_url.indexOf('https://') == 0 || api_url.indexOf('//') == 0)
			request_type = "remote";
		else
			request_type = "local";
	}
	function call_api (query, callback)
	{
		if(!query || !callback)
			throw "Insufficient parameters for API call";
 
		query = serialise_query(query);
 
		if(request_type == "remote")
			request_remote(api_url, query, callback, call_api.on_error || default_on_error);
		else
			request_local(api_url, query, callback, call_api.on_error || default_on_error);
 
	}
 
	var default_on_error = JsMwApi.prototype.on_error || function (xhr, callback, res)
	{
		if (typeof(console) != 'undefined')
			console.log([xhr, res]);
 
		callback(null);
	}
 
	function get_xhr () 
	{
		try{
			return new XMLHttpRequest();
		}catch(e){ try {
			return new ActiveXObject("Msxml2.XMLHTTP");
		}catch(e){ try {
			return new ActiveXObject("Microsoft.XMLHTTP");
		}catch(e){
			throw "Could not create an XmlHttpRequest";
		}}}
	}
 
	function request_local (url, query, callback, on_error)
	{
		var xhr = get_xhr();
 
		xhr.open('POST', url + '?format=json', true);
		xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");                  
		xhr.send(query);
		xhr.onreadystatechange = function ()
		{
			if (xhr.readyState == 4)
			{
				var res;
				if (xhr.status != 200)
					res = {error: {
						code: '_badresponse', 
						info: xhr.status + " " + xhr.statusText
					}};
				else
				{
					try
					{
						res = JSON.parse("("+xhr.responseText+")");
					}
					catch(e)
					{
						res = {error: {
							code: '_badresult',
							info: "The server returned an incorrectly formatted response"
						}};
					}
				}
				if (!res || res.error || res.warnings)
					on_error(xhr, callback, res);
				else
					callback(res);
			}
		}
	}
 
	function request_remote (url, query, callback, on_error)
	{
		if(! window.__JsMwApi__counter)
			window.__JsMwApi__counter = 0;
 
		var cbname = '__JsMwApi__callback' + window.__JsMwApi__counter++; 
 
		window[cbname] = function (res)
		{
			if (res.error || res.warnings)
				on_error(null, callback, res);
			else
				callback(res);
		}
 
		var script = document.createElement('script');
		script.setAttribute('type', 'text/javascript');
		script.setAttribute('src', url + '?format=json&callback=window.' + cbname + '&' + query);
		document.getElementsByTagName('head')[0].appendChild(script);
	}
 
	function serialise_query (obj)
	{
		var amp = "";
		var out = "";
		if (String(obj) === obj)
		{
			out = obj;
		}
		else if (obj instanceof Array)
		{
			for (var i=0; i < obj.length; i++)
			{
				out += amp + serialise_query(obj[i]);
				amp = (out == '' || out.charAt(out.length-1) == '&') ? '' : '&';
			}
		}
		else if (obj instanceof Object)
		{
			for (var k in obj)
			{
				if (obj[k] === true)
					out += amp + encodeURIComponent(k) + '=1';
				else if (obj[k] === false)
					continue;
				else if (obj[k] instanceof Array)
					out += amp + encodeURIComponent(k) + '=' + encodeURIComponent(obj[k].join('|'));
				else if (obj[k] instanceof Object)
					throw "API parameters may not be objects";
				else
					out += amp + encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]);
				amp = '&';
			}
		}
		else if (typeof(obj) !== 'undefined' && obj !== null)
		{
			throw "An API query can only be a string or an object";
		}
		return out;
	}
 
	// Make JSON.parse work
	var JSON = (typeof JSON == 'undefined' ? new Object() : JSON);
 
	if (typeof JSON.parse != 'function')
		JSON.parse = function (json) { return eval('(' + json + ')'); };
 
	// Allow .prototype. extensions
	if (JsMwApi.prototype)
	{
		for (var i in JsMwApi.prototype)
		{
			call_api[i] = JsMwApi.prototype[i];
		}
	}
	return call_api;
}


mw.config.get('wgCanonicalSpecialPageName') == "Watchlist" && $(document).ready(function () {
    var v = document.getElementById("mw-watchlist-options");
    if(!v) return
    v = v.parentNode.insertBefore(newNode('div'), v.nextSibling)
    var opt = JSON.parse(mw.user.options.values['userjs-YRIWW'] || "{}");

    function updateOpt( cb ) {
      $.get( "/w/api.php?format=json&action=query&meta=tokens", function( r ) {
        $.post( "/w/api.php?format=json&action=options&change=userjs-YRIWW=" + JSON.stringify( opt ), {token : r.query.tokens.csrftoken }, function() {
          cb && cb();
        })
      })
    }
    for ( var i in opt ) {
      ( function( i ) {
        var tkn = opt[ i ].token,
          project = "//" + i + ".org/";
        JsMwApi( project + "w/api.php" )({
            action: 'query',
            list: 'watchlist',
            wlowner: mw.config.get('wgUserName'),
            wltoken: tkn,
            wlexcludeuser: mw.user.options.get('watchlisthideown') ? mw.config.get('wgUserName') : 'Example',
            wlprop: 'title|flags|user|parsedcomment|timestamp|ids',
            continue: ''
          }, function ( r ) {
            t = r
            var b = newNode('ul', {
                'style': 'display:' +
                ( opt[ i ].hidden ? 'none;' : 'block;' )
              });
            v.parentNode.insertBefore(b, v.nextSibling);
            v.parentNode.insertBefore(newNode('h3', project + " watchlist", newNode('span', {
                    'style': 'font-size:12px;'
                  }, ' [', newNode('a', 'Remove', {
                      style: 'cursor:pointer;',
                      click: function () {
                        delete opt[ i ];
                        updateOpt( function() { location.reload(); } );
                      }
                    }), '] [', newNode('a', opt[ i ].hidden ? 'Expand ▼' : 'Collapse ▲', {
                      'style': 'cursor:pointer;',
                      'click': function () {
                        if ( b.style.display == 'none') {
                          b.style.display = 'block';
                          this.innerHTML = "Collapse ▲"; // todo: replace innerHTML with something more sanitary, hm?
                          opt[ i ].hidden = false;
                          updateOpt();
                        } else {
                          b.style.display = 'none';
                          this.innerHTML = "Expand ▼";
                          opt[ i ].hidden = true;
                        }
                        updateOpt();
                      }
                    }), ']')), v.nextSibling);
            var g = r.query.watchlist;
            for (var ii = 0; ii < g.length; ii++) {
              var zx = newNode('span', {
                  class: 'comment'
                }), item = g[ ii ];
              zx.innerHTML = ' (' + item.parsedcomment.replace(/\ href\=\"\//g, ' href="' + project) + ')';
              b.appendChild(newNode('li', '(',
                  newNode('a', 'diff', {
                      href: project + "w/index.php?title=" + item.title + "&curid=" + item.pageid + "&diff=" + item.revid
                    }),
                  ' | ',
                  newNode('a', 'hist', {
                      href: project + "w/index.php?title=" + item.title + "&curid=" + item.pageid + "&action=history"
                    }),
                  ') . . ',
                  newNode('a', item.title, {
                      href: project + "wiki/" + item.title
                    }),
                  '; ' + item.timestamp.match(/\d\d\:\d\d/)[0] + ' . . ',
                  item.user,
                  zx))
            }
          })
      })( i );
    }
    var qw, er = ['Wikipedia', 'Wiktionary', 'Wikibooks', 'Wikisource', 'Wikiquote', 'Wikiversity', 'Wikinews', 'Wikivoyage', 'Meta-Wiki', 'Commons', 'Wikispecies', 'Mediawiki', 'Wikidata'], 
      domains = ['meta.wikimedia', 'commons.wikimedia', 'species.wikimedia', 'www.mediawiki', 'www.wikidata'],
      cv, bn, sd;
    document.getElementById('firstHeading').appendChild(newNode('a', "Import watchlist", {
          'style': 'padding-left:10px;cursor:pointer; float:right; font-size: 13px;',
          click: function () {
            v.innerHTML = '';
            v.appendChild(newNode('form', {
                  'style': 'display: inline;'
                },
                'Language: ', cv = newNode('input', {
                    size: 3
                  }),
                ' Project: ', qw = newNode('select'), newNode('br'),
                'Watchlist token ', newNode('small', '(can be found be found at ', sd = newNode('a', 'Special:Preferences', {
                      'href': '//en.wikipedia.org/wiki/Special:Preferences#mw-prefsection-watchlist'
                    }), ' in the Watchlist section)'), ': ', bn = newNode('input'),
                newNode('input', {
                    'type': 'submit',
                    'value': 'Import watchlist'
                  }), newNode('span', {
                    style: 'color:red;'
                  }))).onsubmit = function () {
              if (!/^[a-z]{2,3}(-?[a-z]{2,3})?$/.test(cv.value) && qw.value <= 7 || !bn.value) {
                bn.parentNode.lastChild.innerHTML = bn.value ? "Choose a valid language code." : "Enter watchlist token."
                return false
              }
              var importedurl = (qw.value > 7 ? domains[ qw.value - 7 ] : cv.value + '.' + er[qw.value].toLowerCase())
              opt[ importedurl ] = { 'token' : bn.value };
              updateOpt( function(){ location.reload(); })
              return false;
            }
            for (var i = 0; i < er.length; i++) {
              qw.appendChild(newNode('option', {
                    'value': i
                  }, er[i]))
            };

            function df() {
              if (/^[a-z]{2,3}(-?[a-z]{2,3})?$/.test(cv.value) || qw.value > 7) {
                sd.href = "//" + (qw.value > 7 ? domains[ qw.value - 7 ] : cv.value + '.' + er[qw.value].toLowerCase()) + ".org/wiki/Special:Preferences#mw-prefsection-watchlist";
              }
            }
            cv.onchange = qw.onchange = df;
          }
        }))
  })