ผู้ใช้:Jutiphan/iScript/helper.js

จากวิกิพีเดีย สารานุกรมเสรี

หมายเหตุ: หลังเผยแพร่ คุณอาจต้องล้างแคชเว็บเบราว์เซอร์ของคุณเพื่อดูการเปลี่ยนแปลง

  • ไฟร์ฟอกซ์ / ซาฟารี: กด Shift ค้างขณะคลิก Reload หรือกด Ctrl-F5 หรือ Ctrl-R (⌘-R บนแมค)
  • กูเกิล โครม: กด Ctrl-Shift-R (⌘-Shift-R บนแมค)
  • อินเทอร์เน็ตเอกซ์พลอเรอร์ และ Edge: กด Ctrl ค้างขณะคลิก Refresh หรือกด Ctrl-F5
  • โอเปร่า: กด Ctrl-F5
// ==============================================================
// สคริปต์จัดให้: helper module
// Available functions:
// * JSON - [Object].toJSONString
// * Global Error Handling Support
// * [XMLDOM]
// * dynamicSort
// * [Array].indexOf
// * [Array].every
// * [Array].toSource
// * addTab
// * addToolboxLink
// * addMenu
// * assignToEditForm
// * assignToEditTalkForm
// * createElement
// * getPname
// * getSelText
// * getSelTextArea
// * getUploader
// * getThaiFullMonthName
// * qid_getFileHistory (required for getUploader)
// * SetCaretTo
// * openInNewWindow
// * isIPAddress
// * userIsInGroup
// * [String].trim
// * [String.replaceAll
// * htmlNode
// * [Status]
// * [QueryString]
// * [Wikipedia API]
// * [sprintf]
//
// Copyright (C) 2006-2011, Jutiphan Mongkolsuthree
// Certain functions are copyrighted by their
// respective copyright holders.
//
// No warranty expressed or implied. Use at your own risk.
//
// Created: 1/12/2006
// Replaced: none
//
// Additional Credits Information:
// ==============================================================
// JSON - [Object].toJSONString
// 2008-03-14 Public Domain
// See //www.JSON.org/js.html
// TODO: This is now superceded by json2 and needs to be updated later
//
// ==============================================================
// Array functions
// Taken from //www.dustindiaz.com/basement/sugar-arrays.html
// TODO: This needs to be replaced by uniformed JS 1.6 Library for non-supported browsers
//
// ==============================================================
// getUploader and qid_getFileHistory functions
// Source: en:User:Howcheng/quickimgdelete.js, adapted & further modified by user:Jutiphan
// Current version: 1.13.1
// Created by [[User:Howcheng|Howard Cheng]]
// Released under the [[GNU Public License]] (GPL)
// Full documentation at [[User talk:Howcheng/quickimgdelete.js]]
// NOTE: DO NOT UPGRADE TO NEWER VER WITHOUT MERGE. This is custom, also included sysop fix.
//
// ==============================================================
// setCaretTo function
// Source: //parentnode.org/javascript/working-with-the-cursor-position/
//
// ==============================================================
// replace function
// Source: //www.irt.org/script/242.htm
// ==============================================================
//
// This information may be incomplete. Apologize if incomplete or inaccurate.
// Feel free to inform at [[User Talk:Jutiphan]]
//
/* <pre><nowiki> */

// ========== XMLDOM ==========
window.XMLDOM = function window$XMLDOM(markup) {
    if (!window.DOMParser) {
        var progIDs = [ 'Msxml2.DOMDocument.3.0', 'Msxml2.DOMDocument' ];
        for (var i = 0; i < progIDs.length; i++) {
            try {
                var xmlDOM = new ActiveXObject(progIDs[i]);
                xmlDOM.loadXML(markup);
                xmlDOM.setProperty('SelectionLanguage', 'XPath');
                return xmlDOM;
            }
            catch (ex) {
            }
        }
        return null;
    }
        else {
        try {
            var domParser = new window.DOMParser();
            return domParser.parseFromString(markup, 'text/xml');
        } catch (ex) {
            return null;
        }
    }
    return null;
};


// ========== dynamicSort ==========
// http://stackoverflow.com/questions/1129216/sort-array-of-objects-by-string-property-value-in-javascript
function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        var result = a[property].localeCompare(b[property]);
        return result * sortOrder;
    }
}

// ========== [Array].indexOf ==========
// NOTE: This is included in Javascript 1.6 which is not yet available in IE or ECMAScript
// Taken from //www.dustindiaz.com/basement/sugar-arrays.html
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(el, start) {
    var start = start || 0;
    for (var i = start; i < this.length; ++i) {
      if (this[i] === el) {
        return i;
      }
    }
    return -1;
  };
}



// ========== [Array].every ==========
// NOTE: This is included in Javascript 1.6 which is not yet available in IE or ECMAScript
// Taken from //www.dustindiaz.com/basement/sugar-arrays.html
if (!Array.prototype.every) {
  Array.prototype.every = function(fn, thisObj) {
      var scope = thisObj || window;
      for ( var i=0, j=this.length; i < j; ++i ) {
          if ( !fn.call(scope, this[i], i, this) ) {
              return false;
          }
      }
      return true;
  };
}


// ========== [Array].toSource ==========
if (!Array.prototype.toSource) {
  Array.prototype.toSource = function() {
    var L = this.length,s = '[',t,i;
    for (i = 0; i < L; i++) {
      if (i > 0) s += ',';
      if (this[i].constructor == Array) s += this[i].toSource();
      else switch (typeof this[i]) {
        case "number" : s += this[i]; break;
        case "boolean" : s += this[i]; break;
        default : s += '"' + this[i].toString().split('"').join('\\"') + '"';
      }
    }
    return s + ']';
  };
}

function AddVectorTab(url, name, id, title, key, before) {
    var tabs = document.getElementById('p-views');
    var tabItems = tabs.childNodes[3];


    var vectorTabLink = createElement('a', name, {'title':title, 'href':url});
    var vectorTabTitle = createElement('span', vectorTabLink, '');
    var vectorTab = createElement('li', vectorTabTitle, {'id':id, 'class':'collapsible'});

    if(before == null) {
        var before = document.getElementById('ca-watch');
    }
    if(before == null) before = document.getElementById('ca-unwatch');
    tabItems.insertBefore(vectorTab, before);
}

function addTab(url, name, id, title, key, after) {
    return mw.util.addPortletLink('p-cactions', url, name, id, title, key, after);
}

//WARNING: This function requires iScript sidebox
function addToolboxLink(url, name, id, key) {
  return mw.util.addPortletLink('p-mScripts', url, name, id, key);
}

//WARNING: This function requires iScript sidebox
function addBookmarkLink(url, name, id, key) {
  return mw.util.addPortletLink('p-mBookmarks', url, name, id, key);
}

function addMenu(type, url, name, id, tooltip, accesskey, nextnode) {
  addMenuItem('iScript-menu1', 'แจ้ง▼', type, url, name, id, tooltip, accesskey, nextnode);
}

function addWPMenu(type, url, name, id, tooltip, accesskey, nextnode) {
  addMenuItem('iScript-menu2', 'แจ้งอยู่ในโครงการ▼', type, url, name, id, tooltip, accesskey, nextnode);
}



function addMenuItem(target, title, type, url, name, id, tooltip, accesskey, nextnode) {
  //Use addTab function instead of specify to
  if(iScriptConfig.useOldTabsNavigation) {
    addTab(url, name, id, tooltip, accesskey, nextnode);
    return;
  }

  var na, mn;
  var li;
  var color = " #1e90ff";
  var menu = document.getElementById(target);

  //Create menu if not yet exist
  if(menu) {
    if(skin == "vector") {
        menu = document.getElementById(target);
    } else {
        menu = menu.getElementsByTagName('ul')[0];
    }
  } else {

    if(skin == "vector") {
        title = title.replace("▼","");
        var tabs = document.getElementById('right-navigation');
        var menuTitle = createElement('span', title, "");
        var menuTitle2 = createElement('span', title, "");
        var menuLink = createElement('a', "", {'href':"#"});
        var menuHeading = createElement('h3', menuTitle2 , "");
        menuHeading.appendChild(menuLink);
        var menuHolder = createElement('div', menuHeading, {'id':"p-iScriptMenu", 'role':"navigation", 'class':"vectorMenu"});
        var menuDiv;
        if(target == 'iScript-menu2') {
            menuDiv = createElement('div', "", {'id':target, 'class':"menu wikiMenu"});
        } else {
            menuDiv = createElement('div', "", {'id':target, 'class':"menu"});
        }

        menuHolder.appendChild(menuDiv);

        menu = createElement("ul", "", "");
        menuDiv.appendChild(menu);
        //var menuTab = createElement("li", [menu, title], {'id':target, 'class':"tabmenu", 'title':"แจ้ง..."});

        var iScriptMenu = document.getElementById(target);
        //menuHolder.appendChild(menuTab);

        if(iScriptMenu && (target == "ca-menu" || target == "ca-menu2")) {
            iScriptMenu.appendChild(menu);
            menu = document.getElementById(target);
        } else {
            var beforeElement = document.getElementById("p-search");
            tabs.insertBefore(menuHolder, beforeElement);
        }


    } else {
        var tabs = document.getElementById('p-cactions').getElementsByTagName('ul')[0];
        menu = createElement("ul", "", {'class':"tabmenu"});
        var menuTab = createElement("li", [menu, title], {'id':target, 'class':"tabmenu", 'title':"แจ้ง..."});
        var menu2 = document.getElementById("ca-menu2");
        if(menu2 && target == "ca-menu") {
          tabs.insertBefore(menuTab, menu2);
        } else {
          tabs.appendChild(menuTab);
        }
    }
    //IE6 Support
    if (!window.XMLHttpRequest && document.all) {
      //menu.style.marginLeft = "1.6em";
      menuTab.onmouseover = function() {
        menuTab.style.zIndex = 3;
        menu.style.display = "block";
      };
      menuTab.onmouseout = function() {
        menuTab.style.zIndex = 0;
        menu.style.display = "none";
      };
    }
  }

  if(type) {
    switch(type) {
      case 'serious': color = '#b22222'; break;
      case 'content' : color = '#f28500'; break;
      case 'style' : color = '#f4c430'; break;
      case 'merge' : color = '#9932cc'; break;
      case 'notice' : color = '#1e90ff'; break;
      case 'growth': color = '#228b22'; break;
      default: color = type;
    }
  }

  if (!id) id = name;
  if (!url) url = '#';
  (na = document.createElement("a")).appendChild(document.createTextNode(name));
  na.href = url;

	if (accesskey) {
		na.setAttribute( "accesskey", accesskey);
		tooltip += " ["+accesskey+"]";
	}
  if(tooltip) na.setAttribute('title', tooltip);

  mn = document.createElement("ul");
  (li = document.createElement("li")).appendChild(na);
  li.style.cssText = "border-left: 10px solid " + color + ";";
  //li.appendChild(mn);
  if (id) li.id = id;

  if(skin == "vector" && menu.localName != "ul") {
      menu = menu.getElementsByTagName('ul')[0];
  }
  if (nextnode) {
    menu.insertBefore(li, nextnode);
  } else {
    menu.appendChild(li);
  }

  return mn;  // useful because it gives us the <ul> to add <li>s to
}

// ========== assignToEditForm designed for iScript ==========
function assignToEditForm(iScriptAction, param1, param2, param3, param4) {

  if(iScriptConfig.useAjaxApi && iScriptAction.indexOf("addMessageTemplate") == 0) {
      AddTemplate(wgPageName, param1, param2, param3, param4);
      return;
  }

  var link = wgScript + "?title=" +  encodeURIComponent(wgPageName) + "&action=edit&iScriptAction=" + iScriptAction;
  if(param1 != null) {
      link += "&iScriptParam1=" + param1;
  }
  if(param2 != null) {
      link += "&iScriptParam2=" + param2;
  }
  if(param3 != null) {
      link += "&iScriptParam3=" + param3;
	}  
	if(param4 != null) {
		link += "&iScriptParam4=" + param4;
}  
  location.assign(link);
}

//Send the page with provided action and params to Talk Page.
//Added fragile partial support for non-main namespaces
//Added incomplete support for universal use even on talk page.
function assignToEditTalkForm(iScriptAction, param1, param2, param3) {
  var ns = "พูดคุย:";
  if(wgNamespaceNumber > 0) {
    ns = wgPageName.substr(0, wgPageName.indexOf(":") + 1);
    if(wgNamespaceNumber % 2 == 0) {
        if(ns.indexOf("ผู้ใช้") >= 0) {
          ns = "คุยกับ" + ns;
        } else {
          ns = "คุยเรื่อง" + ns;
        }
    }
  }

  if(iScriptConfig.useAjaxApi == true) {
    if(iScriptAction.indexOf("doWP") == 0) {
        AddWPBanner(ns + mw.config.get('wgTitle'), param1, param2, param3);
    } else if (iScriptAction.indexOf("addMessageTemplate") == 0) {
        AddTemplate(ns + mw.config.get('wgTitle'), param1, param2, param3);
    }
    return;
  }

  ns = encodeURIComponent(ns);

  if(wgAction.indexOf("edit") == 0) {
    if(iScriptAction.indexOf("doWP") == 0) {
      doWP(param1, param2, param3);
      return;

    } else if (iScriptAction.indexOf("addMessageTemplate") == 0) {
      addMessageTemplate(param1, param2, param3);
      return;
    }
  }

  var link = wgScript + "?title=" + ns + encodeURIComponent(mw.config.get('wgTitle')) + "&action=edit&iScriptAction=" + iScriptAction;
  if(param1 != null) {
      link += "&iScriptParam1=" + param1;
  }
  if(param2 != null) {
      link += "&iScriptParam2=" + param2;
  }
  if(param3 != null) {
      link += "&iScriptParam3=" + param3;
  }
  location.assign(link);
}



/** extended createElement function *************************************
 *
 *  Description:  createElement function with additional params
 *  Added by:    [[User:Jutiphan]]
 *  Maintainers: [[User:Jutiphan]]
 *  Source: //zh.wikipedia.org/wiki/mediawiki:common.js
 */
function createElement(tag, children, props) {
    var element = document.createElement(tag);
    if (!(children instanceof Array)) {
        children = [children];
    }
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        if (typeof child == 'string') {
            child = document.createTextNode(child);
        }
        if (child) {
            element.appendChild(child);
        }
    }
    if (typeof props == 'object') {
        for (var k in props) {
            switch (k) {
                case 'styles':
                    var styles = props.styles;
                    for (var s in styles) {
                        element.style[s] = styles[s];
                    }
                    break;
                case 'events':
                    var events = props.events;
                    for (var e in events) {
                        addHandler(element, e, events[e]);
                    }
                    break;
                case 'class':
                    element.className = props[k];break;
                case 'toJSONString':
                    break;
                default:
                    element.setAttribute(k, props[k]);
            }
        }
    }
    return element;
}



//Returns the name of the page. For example, if you were browsing the "[[foo]]" WP page, getPname() would return "foo"
function getPname() {
  if (typeof wgPageName != 'undefined' && wgPageName != null) {
    return wgPageName.replace(/_/g, ' ');
  } else {
    return document.getElementsByTagName('h1')[0].firstChild.nodeValue;
    //return document.title.substr(0, document.title.lastIndexOf(' - วิกิพีเดีย'));
    /*
    z=document.getElementById("content").childNodes;
    for (var n=0;n<z.length;n++) {
      if (z[n].className=="firstHeading") return URLEncoding(z[n].innerHTML);
    }
    */
  }
}


//Return selected text if any. User getSelTextArea to get selected text in textbox
function getSelText() {
    var text = "";
    if (window.getSelection
        && window.getSelection().toString()
        && $(window.getSelection()).attr('type') != "Caret") {
        text = window.getSelection().toString();;
        return text;
    } else if (document.getSelection
        && document.getSelection().toString()
        && $(document.getSelection()).attr('type') != "Caret") {
        text = document.getSelection().toString();;
        return text;
    } else {
        var selection = document.selection && document.selection.createRange();

        if (!(typeof selection === "undefined")
            && selection.text
            && selection.text.toString()) {
            text = selection.text;
            return text;
        }
    }

    if (window.getSelection) {
        selText = window.getSelection();
    } else if (document.getSelection) {
        selText = document.getSelection();
    } else if (document.selection) {
        selText = document.selection.createRange();
        selText = selText.text;
    } else {
        return "";
    }
}



//Get selected text in textbox.
function getSelTextArea() {
  var txtArea = document.editform.wpTextbox1;
  if($.client.profile().layout == 'gecko') {
    if (txtArea.selectionStart || txtArea.selectionStart == '0') {
      var startPos = txtArea.selectionStart;
      var endPos = txtArea.selectionEnd;
      return (txtArea.value).substring(startPos, endPos);
    }

  } else {
    var selText = "";
    if (window.getSelection) {
      selText = window.getSelection();
    } else if (document.getSelection) {
      selText = document.getSelection();
    } else if (document.selection) {
      selText = document.selection.createRange();
      selText = selText.text;
    }
    return selText;
  }

  return "";
}



//===== getUploader =====
//Source: en:User:Howcheng/quickimgdelete.js, adapted & further modified by user:Jutiphan
//NOTE: DO NOT UPGRADE TO NEWER VER WITHOUT MERGE. This is custom, include sysop fix. See below.
 /*
 * Current version: 1.10.4
 * Updated to version: 1.14.2 (Hotfixes only)
 * =======================================
 * Created by [[User:Howcheng|Howard Cheng]]
 * Released under the [[GNU Public License]] (GPL)
 * Full documentation at [[User talk:Howcheng/quickimgdelete.js]]
 * =======================================
 */
  //NOTE: DOES NOT work in Edit page
  // Get uploader from first point in the list under "File history"
  // Uploader is stored in second A tag in UL tag under "File history"
  // Returns title of user page (without name space) in URL form
function getUploader() {
  // Returns title of user page (without name space) in URL form
  var trs = qid_getFileHistory();
  var els = new Array();
  var tr = trs[0]; // skip first one because it's the header
  do {
    tr = tr.nextSibling;
    var tds = tr.childNodes;
    var td = tds[(userIsInGroup("sysop") ? 5 : 4)]; // uploader info
    els[els.length] = td;
  } while (tr.nextSibling);

  var uploaders = new Array();
  var re1 = new RegExp(('/wiki/').replace(/\./g, '\\.') + 'ผู้ใช้:(.*)$');
  var re2 = new RegExp((wgServer + wgScript).replace(/\./g, '\\.') + '\\?title=ผู้ใช้:([^&]*)');
  var re3 = /(คุยกับ)?ผู้ใช้:(.*?)( \((ยังไม่ได้สร้าง|หน้านี้ไม่มี)\))?$/; // this is for IE and handling Unicode characters

  var m;
  var uploader;
  var uploaderList = "";
  var count = 0;
  for (var i = 0; i < els.length; i++) {
    var el = els[i];
    if (!el) continue;
    var as = el.childNodes;
    if (!as) continue;
    for (var k=0; k<as.length; k++) {
       if (as[k].tagName != 'A') continue;
       m = re3.exec(as[k].title);
       if (m) uploader = encodeURIComponent(m[2]);
       m = re1.exec(as[k].href);
       if (m) uploader = m[1];
       m = re2.exec(as[k].href);
       if (m) uploader = m[1];

       if (uploader) break;
    }

    if (uploaderList.indexOf(uploader) == -1) {
      if (count > 0) uploaderList += "; ";
      uploaderList += count + " - " + uploader;
      uploaders[uploaders.length] = uploader;
      count += 1;
    }
  }

  if (!uploaders || uploaders.length == 0) {
    alert("getUploader: ไม่สามารถดึงชื่อผู้ใช้ที่อัปโหลดได้ กรุณาแจ้งผู้ใช้:Jutiphan");
    return null;
  }
  if (uploaders.length == 1)
    return uploaders[0];

  var which = parseInt(window.prompt("กรุณาเลือกชื่อผู้ใช้ที่ต้องการจะแจ้ง: " + uploaderList, ""));
  if (isNaN(which) || which < 0 || which >= uploaders.length) {
    alert("getUploader: ไม่มีชื่อผู้ใช้ที่เลือก หยุดการดำเนินการ");
    return null;
  }
  return uploaders[which];
}


function GetThaiFullMonthName(monthNo) {
  switch (monthNo) {
    case 1:
      return "มกราคม";
    case 2:
      return "กุมภาพันธ์";
    case 3:
      return "มีนาคม";
    case 4:
      return "เมษายน";
    case 5:
      return "พฤษภาคม";
    case 6:
      return "มิถุนายน";
    case 7:
      return "กรกฎาคม";
    case 8:
      return "สิงหาคม";
    case 9:
      return "กันยายน";
    case 10:
      return "ตุลาคม";
    case 11:
      return "พฤศจิกายน";
    case 12:
      return "ธันวาคม";
    default:
      return "";
  }
}

//===== qid_getFileHistory =====
//Part of getUploader. See above.
function qid_getFileHistory() {
  var el = document.getElementById('mw-imagepage-section-filehistory');
  if (!el) {
    alert("getUploader: ไม่พบประวัติไฟล์ หยุดการดำเนินงาน กรุณาแจ้งผู้ใช้:Jutiphan");
    return null;
  }

    el = el.firstChild;
    while (el.nextSibling) {
      el = el.nextSibling;
      if (el.tagName && el.tagName.toLowerCase() == 'table')
        break;
    }


  if (!el) {
    alert("getUploader: ไม่พบป้าย TABLE หยุดการทำงาน กรุณาแจ้งผู้ใช้:Jutiphan");
    return null;
  }

  var trs = el.getElementsByTagName('tr');
  if (!trs) {
    alert("getUploader: ไม่พบป้าย TR หยุดการทำงาน กรุณาแจ้งผู้ใช้:Jutiphan");
    return null;
  }
  return trs;
}



//===== Set Cursor Position in given Textbox =====
//Source: //parentnode.org/javascript/working-with-the-cursor-position/
function setCaretTo(obj, pos) {
  if (obj.createTextRange) {
    /* Create a TextRange, set the internal pointer to
       a specified position and show the cursor at this
       position
    */
    var range = obj.createTextRange();
    range.move("character", pos);
    range.select();
  } else if (obj.selectionStart) {
    /* Gecko is a little bit shorter on that. Simply
       focus the element and set the selection to a
       specified position
    */
    obj.focus();
    obj.setSelectionRange(pos, pos);
  }
}


// ========== Open In New Window ==========
function openInNewWindow(website, windowName, isFocus) {
  if(!windowName) {
    windowName = '_blank';
  }
  var newWindow = window.open(website, windowName);
  if(isFocus) {
    newWindow.focus();
  }
}


// ========== Check if the user is an IP Address ==========
/* Returns true if given string contains a valid IP-address, that is, from 0.0.0.0 to 255.255.255.255*/
function isIPAddress(string) {
  var res = /(\d{1,4})\.(\d{1,3})\.(\d{1,3})\.(\d{1,4})/.exec(string);
  return res != null && res.slice(1, 5).every(function(e) {
    return e < 256;
  });
}



// ========== Check if the user belongs to the given group ==========
function userIsInGroup(groupName) {
  for (var i = 0; i < wgUserGroups.length; i++) {
    if (wgUserGroups[i] == groupName)
      return true;
  }
  return false;
}



// ========== Replace string ==========
// Javascript from //www.irt.org/script/242.htm
function replace(string, text, by) {
  // Replaces text with by in string
  var strLength = string.length, txtLength = text.length;
  if ((strLength == 0) || (txtLength == 0)) return string;

  var i = string.indexOf(text);
  if ((!i) && (text != string.substring(0, txtLength))) return string;
  if (i == -1) return string;

  var newstr = string.substring(0, i) + by;

  if (i + txtLength < strLength)
    newstr += replace(string.substring(i + txtLength, strLength), text, by);

  return newstr;
}



// ========== [String].trim ==========
String.prototype.trim = function() {
  return this.replace(/^\s+|\s+$/, '');
};



// Replaces all instances of the given substring.
String.prototype.replaceAll = function(
    strTarget, // The substring you want to replace
    strSubString // The string you want to replace in.
    ) {
  var strText = this;
  var intIndexOfMatch = strText.indexOf(strTarget);

  // Keep looping while an instance of the target string
  // still exists in the string.
  while (intIndexOfMatch != -1) {
    // Relace out the current instance.
    strText = strText.replace(strTarget, strSubString);

    // Get the index of any next matching substring.
    intIndexOfMatch = strText.indexOf(strTarget);
  }

  // Return the updated string with ALL the target strings
  // replaced out with the new substring.
  return( strText );
};



// Simple helper function to create a simple node
function htmlNode(type, content, color) {
  var node = document.createElement(type);
  if (color) {
    node.style.color = color;
  }
  node.appendChild(document.createTextNode(content));
  return node;
}


// ========== Error Handling Support ==========
function errorHandler(message, url, line)
{
  Status.error("ขออภัย พบเจอข้อผิดพลาด " + message + " บรรทัดที่ " + line + " ใน " + url + ". กรุณาแจ้งปัญหานี้ที่ ผู้ใช้:Jutiphan");
  return true;
}

//Install the global error-handler
window.onerror = errorHandler;

//Simple exception handling
Exception = function( message ) {
	this.message = message || '';
	this.name = "Exception";
};

Exception.prototype.what = function() {
	return this.str;
};



// ========== Status class ==========
function Status( text, stat, type ) {
	this.text = this.codify(text);
	this.stat = this.codify(stat);
	this.type = type || 'status';
	this.generate();
	if( stat ) {
		this.render();
	}
}
Status.init = function( root ) {
	if( !( root instanceof Element ) ) {
		throw new Exception( 'object not an instance of Element' );
	}
	while( root.hasChildNodes() ) {
		root.removeChild( root.firstChild );
	}
	Status.root = root;

	var cssNode = document.createElement('style');
	cssNode.type = 'text/css';
	cssNode.rel = 'stylesheet';
	cssNode.appendChild( document.createTextNode("")); // Safari bugfix
	document.getElementsByTagName("head")[0].appendChild(cssNode);
	var styles = cssNode.sheet ? cssNode.sheet : cssNode.stylesSheet;
	styles.insertRule(".tw_status_status { color: SteelBlue; }", 0);
	styles.insertRule(".tw_status_info { color: ForestGreen; }", 0);
	styles.insertRule(".tw_status_warn { color: OrangeRed; }", 0);
	styles.insertRule(".tw_status_error { color: OrangeRed; font-weight: 900; }", 0);
};
Status.root = null;

Status.prototype = {
	stat: null,
	text: null,
	type: 'status',
	target: null,
	node: null,
	linked: false,
	link: function() {
		if( ! this.linked && Status.root ) {
			Status.root.appendChild( this.node );
			this.linked = true;
		}
	},
	unlink: function() {
		if( this.linked ) {
			Status.root.removeChild( this.node );
			this.linked = false;
		}
	},
	codify: function( obj ) {
		if ( ! ( obj instanceof Array ) ) {
			obj = [ obj ];
		}
		var result;
		result = document.createDocumentFragment();
		for( var i = 0; i < obj.length; ++i ) {
			if( typeof obj[i] == 'string' ) {
				result.appendChild( document.createTextNode( obj[i] ) );
			} else if( obj[i] instanceof Element ) {
				result.appendChild( obj[i] );
			} // Else cosmic radiation made something shit
		}
		return result;

	},
	update: function( status, type ) {
		this.stat = this.codify( status );
		if( type ) {
			this.type = type;
		}
		this.render();
	},
	generate: function() {
		this.node = document.createElement( 'div' );
		this.node.appendChild( document.createElement('span') ).appendChild( this.text );
		this.node.appendChild( document.createElement('span') ).appendChild( document.createTextNode( ': ' ) );
		this.target = this.node.appendChild( document.createElement( 'span' ) );
		this.target.appendChild(  document.createTextNode( '' ) ); // dummy node
	},
	render: function() {
		this.node.className = 'tw_status_' + this.type;
		while( this.target.hasChildNodes() ) {
			this.target.removeChild( this.target.firstChild );
		}
		this.target.appendChild( this.stat );
		this.link();
	},
	status: function( status ) {
		this.update( status, 'status');
	},
	info: function( status ) {
		this.update( status, 'info');
	},
	warn: function( status ) {
		this.update( status, 'warn');
	},
	error: function( status ) {
		this.update( status, 'error');
	}
};

Status.status = function( text, status ) {
	return new Status( text, status, 'status' );
};
Status.info = function( text, status ) {
	return new Status( text, status, 'info' );
};
Status.warn = function( text, status ) {
	return new Status( text, status, 'error' );
};
Status.error = function( text, status ) {
	return new Status( text, status, 'error' );
};




/**
* Maps the querystring to an object
*
* Functions:
*
* QueryString.exists(key)
*     returns true if the particular key is set
* QueryString.get(key)
*     returns the value associated to the key
* QueryString.equals(key, value)
*     returns true if the value associated with given key equals given value
* QueryString.toString()
*     returns the query string as a string
* QueryString.create( hash )
*     creates an querystring and encodes strings via encodeURIComponent and joins arrays with |
*
* In static context, the value of location.search.substring(1), else the value given to the constructor is going to be used. The mapped hash is saved in the object.
*
* Example:
*
* var value = QueryString.get('key');
* var obj = new QueryString('foo=bar&baz=quux');
* value = obj.get('foo');
*/
function QueryString(qString) {
	this.string = qString;
	this.params = {};

	if( qString.length == 0 ) {
		return;
	}

	qString.replace(/\+/, ' ');
	var args = qString.split('&');

	for( var i = 0; i < args.length; ++i ) {
		var pair = args[i].split( '=' );
		var key = decodeURIComponent( pair[0] ), value = key;

		if( pair.length == 2 ) {
			value = decodeURIComponent( pair[1] );
		}

		this.params[key] = value;
	}
}

QueryString.static = null;

QueryString.staticInit = function() {
	if( QueryString.static == null ) {
		QueryString.static = new QueryString(location.search.substring(1));
	}
};

QueryString.get = function(key) {
	QueryString.staticInit();
	return QueryString.static.get(key);
};

QueryString.prototype.get = function(key) {
	return this.params[key] ? this.params[key] : null;
};

QueryString.exists = function(key) {
	QueryString.staticInit();
	return QueryString.static.exists(key);
};

QueryString.prototype.exists = function(key) {
	return this.params[key] ? true : false;
};

QueryString.equals = function(key, value) {
	QueryString.staticInit();
	return QueryString.static.equals(key, value);
};

QueryString.prototype.equals = function(key, value) {
	return this.params[key] == value ? true : false;
};

QueryString.toString = function() {
	QueryString.staticInit();
	return QueryString.static.toString();
};

QueryString.prototype.toString = function() {
	return this.string ? this.string : null;
};


QueryString.create = function( arr ) {
	var resarr = Array();
	var editToken;  // KLUGE: this should always be the last item in the query string (bug TW-B-0013)
	for( var i in arr ) {
		if( typeof arr[i] == 'undefined' ) {
			continue;
		}
		var res;
		if( arr[i] instanceof Array ){
			var v =  Array();
			for(var j = 0; j < arr[i].length; ++j ) {
				v[j] = encodeURIComponent( arr[i][j] );
			}
			res = v.join('|');
		} else {
			res = encodeURIComponent( arr[i] );
		}
                if( i == 'wpEditToken' ) {
                        editToken = res;
		} else {
			resarr.push( encodeURIComponent( i ) + '=' + res );
		}
	}
	if( typeof editToken != 'undefined' ) {
		resarr.push( 'wpEditToken=' + editToken );
	}
	return resarr.join('&');
};
QueryString.prototype.create = QueryString.create;



// Accessor functions for wikiediting and api-access
Wikipedia = {};

// we dump all XHR here so they won't loose props
Wikipedia.dump = [];

Wikipedia.numberOfActionsLeft = 0;
Wikipedia.nbrOfCheckpointsLeft = 0;

Wikipedia.actionCompleted = function( self ) {
	if( --Wikipedia.numberOfActionsLeft <= 0 && Wikipedia.nbrOfCheckpointsLeft <= 0 ) {
		Wikipedia.actionCompleted.event( self );
	}
};

// Change per action wanted
Wikipedia.actionCompleted.event = function() {
	new Status( Wikipedia.actionCompleted.notice, Wikipedia.actionCompleted.postfix, 'info' );
	if( Wikipedia.actionCompleted.redirect != null ) {
		// if it isn't an url, make it an relative to self (probably this is the case)
		if( !/^\w+\:\/\//.test( Wikipedia.actionCompleted.redirect ) ) {
			Wikipedia.actionCompleted.redirect = wgServer + wgArticlePath.replace( '$1', encodeURIComponent( Wikipedia.actionCompleted.redirect ).replace( /\%2F/g, '/' ) );
		}
		window.setTimeout( function() { window.location = Wikipedia.actionCompleted.redirect; } , Wikipedia.actionCompleted.timeOut );
	}
};
wpActionCompletedTimeOut = typeof(wpActionCompletedTimeOut) == 'undefined'  ? 5000 : wpActionCompletedTimeOut;
wpMaxLag = typeof(wpMaxLag) == 'undefined' ? 10 : wpMaxLag; // Maximum lag allowed, 5-10 is a good value, the higher value, the more agressive.

Wikipedia.editCount = 10;
Wikipedia.actionCompleted.timeOut = wpActionCompletedTimeOut;
Wikipedia.actionCompleted.redirect = null;
Wikipedia.actionCompleted.notice = 'Action';
Wikipedia.actionCompleted.postfix = 'เรียบร้อย';

Wikipedia.addCheckpoint = function() {
	++Wikipedia.nbrOfCheckpointsLeft;
};

Wikipedia.removeCheckpoint = function() {
	if( --Wikipedia.nbrOfCheckpointsLeft <= 0 && Wikipedia.numberOfActionsLeft <= 0 ) {
		Wikipedia.actionCompleted.event();
	}
};

/*
 currentAction: text, the current action (required)
 query: Object, the query (required)
 oninit: function, the function to call when page gotten
 */
Wikipedia.api = function( currentAction, query, oninit, statelem ) {
	this.currentAction = currentAction;
	this.query = query;
	this.query['format'] = 'xml'; //LET THE FORCE BE WITH YOU!!!
	this.oninit = oninit;
	if( statelem ) {
		statelem.status( currentAction );
	} else {
		this.statelem = new Status( currentAction );
	}
	++Wikipedia.numberOfActionsLeft;
};
Wikipedia.api.prototype = {
	currentAction: '',
	oninit: null,
	query: null,
	responseXML: null,
	statelem:  null,
	counter: 0,
	post: function() {
		var xmlhttp = sajax_init_object();
		Wikipedia.dump.push( xmlhttp );
		xmlhttp.obj = this;
		xmlhttp.overrideMimeType('text/xml');
		xmlhttp.open( 'POST' , wgServer + wgScriptPath + '/api.php', true);
		xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded');
		xmlhttp.onerror = function() {
			this.obj.statelem.error( "พบเจอข้อผิดพลาด " + this.target.status + " ระหว่างกำลังดึงข้อมูลผ่าน  API" );
		};
		xmlhttp.onload = function() {
			this.obj.responseXML = this.responseXML;
			if( this.obj.oninit ) {
				this.obj.oninit( this.obj );
			}
			Wikipedia.actionCompleted();
		};
		xmlhttp.send( QueryString.create( this.query ) );
	}
};

/*
 currentAction: text, the current action (required)
 query: Object, the query (required)
 oninit: function, the function to call when page gotten (required)
 onsuccess: function, a function to call when post succeeded
 onerror: function, a function to call when we abort failed posts
 onretry: function, a function to call when we try to retry a post
 */
Wikipedia.wiki = function( currentAction, query, oninit, onsuccess, onerror, onretry ) {
	this.currentAction = currentAction;
	this.query = query;
	this.oninit = oninit;
	this.onsuccess = onsuccess;
	this.onerror = onerror;
	this.onretry = onretry;
	this.statelem = new Status( currentAction );
	++Wikipedia.numberOfActionsLeft;
};

Wikipedia.wiki.prototype = {
	currentAction: '',
	onsuccess: null,
	onerror: null,
	onretry: null,
	oninit: null,
	query: null,
	postData: null,
	responseXML: null,
	statelem: null,
	counter: 0,
	post: function( data ) {
		this.postData = data;
		if( Wikipedia.editCount <= 0 ) {
			this.query['maxlag'] = wpMaxLag; // are we a bot?
		} else {
			--Wikipedia.editCount;
		}

		var xmlhttp = sajax_init_object();
		Wikipedia.dump.push( xmlhttp );
		xmlhttp.obj = this;
		xmlhttp.overrideMimeType('text/xml');
		xmlhttp.open( 'POST' , wgServer + wgScriptPath + '/index.php?useskin=monobook&' + QueryString.create( this.query ), true);
		xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded');
		xmlhttp.onerror = function(e) {
			var self = this.obj;
			self.statelem.error( "Error " + e.target.status + " occurred while posting the document." );
		};
		xmlhttp.onload = function(e) {
			var self = this.obj;
			var status = e.target.status;
			if( status != 200 ) {
				if( status == 503 ) {
					var retry = e.target.getResponseHeader( 'Retry-After' );
					var lag = e.target.getResponseHeader( 'X-Database-Lag' );
					if( lag ) {
						self.statelem.warn( "current lag of " + lag + " seconds is more than our defined maximum lag of " + wpMaxLag + " seconds, will retry in " + retry + " seconds" );
						window.setTimeout( function( self ) { self.post( self.postData ); }, retry * 1000, self );
						return;
					} else {
						self.statelem.error( "Error " + status + " occurred while posting the document." );
					}
				}
				return;
			}
			var xmlDoc;
			xmlDoc = self.responseXML = this.responseXML;
			var xpathExpr =  'boolean(//div[@class=\'previewnote\']/p/strong[contains(.,\'Sorry! We could not process your edit due to a loss of session data\')])';
			var nosession = xmlDoc.evaluate( xpathExpr, xmlDoc, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue;
			if( nosession ) {
				// Grabbing the shipping token, and repost
				var new_token = xmlDoc.evaluate( '//input[@name="wfEditToken"]/@value', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue;
				self.postData['wfEditToken'] = new_token;
				self.post( self.postData );
			} else {
				if( self.onsuccess ) {
					self.onsuccess( self );
				} else {
					var link = document.createElement( 'a' );
					link.setAttribute( 'href', wgArticlePath.replace( '$1', self.query['title'] ) );
					link.setAttribute( 'title', self.query['title'] );
					link.appendChild( document.createTextNode( self.query['title'] ) );

					self.statelem.info( [ 'เรียบร้อย (' , link , ')' ] );
				}
				Wikipedia.actionCompleted();
			}
		};
		xmlhttp.send( QueryString.create( this.postData ) );
	},
	get: function() {
		this.onloading( this );
		var redirect_query = {
			'action': 'query',
			'titles': this.query['title'],
			'redirects': ''
		};

		var wikipedia_api = new Wikipedia.api( "กำลังตรวจสอบและข้ามหน้าเปลี่ยนทาง", redirect_query, this.postget, this.statelem );
		wikipedia_api.parent = this;
		wikipedia_api.post();
	},
	postget: function() {
		var xmlDoc = self.responseXML = this.responseXML;
		var to = xmlDoc.evaluate( '//redirects/r/@to', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue;
		if( !this.followRedirect ) {
			this.parent.statelem.info('ไม่สนใจหน้าเปลี่ยนทาง');
		} else if( to ) {
			this.parent.query['title'] = to;
		}
		this.parent.onloading( this );
		var xmlhttp = sajax_init_object();
		Wikipedia.dump.push( xmlhttp );
		xmlhttp.obj = this.parent;
		xmlhttp.overrideMimeType('text/xml');
		xmlhttp.open( 'GET' , wgServer + wgScriptPath + '/index.php?useskin=monobook&' + QueryString.create( this.parent.query ), true);
		xmlhttp.onerror = function() {
			var self = this.obj;
			self.statelem.error( "Error " + this.status + " occurred while receiving the document." );
		};
		xmlhttp.onload = function() {
			this.obj.onloaded( this.obj );
			this.obj.responseXML = this.responseXML;
			this.obj.responseText = this.responseText;
			this.obj.oninit( this.obj );
		};
		xmlhttp.send( null );
	},
	onloading: function() {
		this.statelem.status( 'กำลังโหลด...' );
	},
	onloaded: function() {
		this.statelem.status( 'ข้อมูลโหลดแล้ว...' );
	}
};



// Sprintf implementation based on perl similar
function sprintf() {
	if( arguments.length == 0 ) {
		throw "Not enough arguments for sprintf";
	}
	var result = "";
	var format = arguments[0];

	var index = 1;
	var current_index = 1;
	var flags = {};
	var in_operator = false;
	var relative = false;
	var precision = false;
	var fixed = false;
	var vector = false;
	var vector_delimiter = '.';


	for( var i = 0; i < format.length; ++i ) {
		var current_char = format.charAt(i);
		if( in_operator ) {
			switch( current_char ) {
			case 'i':
				current_char = 'd';
				break;
			case 'F':
				current_char = 'f';
				break;
			case '%':
			case 'c':
			case 's':
			case 'd':
			case 'u':
			case 'o':
			case 'x':
			case 'e':
			case 'f':
			case 'g':
			case 'X':
			case 'E':
			case 'G':
			case 'b':
				var value = arguments[current_index];
				if( vector ) {
					r = value.toString().split( '' );
					result += value.toString().split('').map( function( value ) {
							return sprintf.format( current_char, value.charCodeAt(), flags );
						}).join( vector_delimiter );
				} else {
					result += sprintf.format( current_char, value, flags );
				}
				if( !fixed ) {
					++index;
				}
				current_index = index;
				flags = {};
				relative = false;
				in_operator = false;
				precision = false;
				fixed = false;
				vector = false;
				vector_delimiter = '.';
				break;
			case 'v':
				vector = true;
				break;
			case ' ':
			case '0':
			case '-':
			case '+':
			case '#':
				flags[current_char] = true;
				break;
			case '*':
				relative = true;
				break;
			case '.':
				precision = true;
				break;
			}
			if( /\d/.test( current_char ) ) {
				var num = parseInt( format.substr( i ) );
				var len = num.toString().length;
				i += len - 1;
				var next = format.charAt( i  + 1 );
				if( next == '$' ) {
					if( num <= 0 || num >= arguments.length ) {
						throw "out of bound";
					}
					if( relative ) {
						if( precision ) {
							flags['precision'] = arguments[num];
							precision = false;
						} else if( format.charAt( i + 2 ) == 'v' ) {
							vector_delimiter = arguments[num];
						}else {
							flags['width'] = arguments[num];
						}
						relative = false;
					} else {
						fixed = true;
						current_index = num;
					}
					++i;
				} else if( precision ) {
					flags['precision'] = num;
					precision = false;
				} else {
					flags['width'] = num;
				}
			} else if ( relative && !/\d/.test( format.charAt( i + 1 ) ) ) {
				if( precision ) {
					flags['precision'] = arguments[current_index];
					precision = false;
				} else if( format.charAt( i + 1 ) == 'v' ) {
					vector_delimiter = arguments[current_index];
				} else {
					flags['width'] = arguments[current_index];
				}
				++index;
				if( !fixed ) {
					current_index++;
				}
				relative = false;
			}
		} else {
			if( current_char == '%' ) {
				in_operator = true;
				continue;
			} else {
				result += current_char;
				continue;
			}
		}
	}
	return result;
}

sprintf.format = function sprintfFormat( type, value, flags ) {

	// Similar to how perl printf works
	if( value == undefined ) {
		if( type == 's' ) {
			return '';
		} else {
			return '0';
		}
	}

	var result;
	var prefix = '';
	var fill = '';
	var fillchar = ' ';
	switch( type ) {
	case '%':
		result = '%';
		break;
	case 'c':
		result = String.fromCharCode( parseInt( value ) );
		break;
	case 's':
		result = value.toString();
		break;
	case 'd':
		result = parseInt( value ).toString();
		break;
	case 'u':
		result = Math.abs( parseInt( value ) ).toString(); // it's not correct, but JS lacks unsigned ints
		break;
	case 'o':
		result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(8);
		break;
	case 'x':
		result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(16);
		break;
	case 'b':
		result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(2);
		break;
	case 'e':
		var digits = flags['precision'] ? flags['precision'] : 6;
		result = (new Number( value ) ).toExponential( digits ).toString();
		break;
	case 'f':
		var digits = flags['precision'] ? flags['precision'] : 6;
		result = (new Number( value ) ).toFixed( digits ).toString();
	case 'g':
		var digits = flags['precision'] ? flags['precision'] : 6;
		result = (new Number( value ) ).toPrecision( digits ).toString();
		break;
	case 'X':
		result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(16).toUpperCase();
		break;
	case 'E':
		var digits = flags['precision'] ? flags['precision'] : 6;
		result = (new Number( value ) ).toExponential( digits ).toString().toUpperCase();
		break;
	case 'G':
		var digits = flags['precision'] ? flags['precision'] : 6;
		result = (new Number( value ) ).toPrecision( digits ).toString().toUpperCase();
		break;
	}

	if(flags['+'] && parseFloat( value ) > 0 && ['d','e','f','g','E','G'].indexOf(type) != -1 ) {
		prefix = '+';
	}

	if(flags[' '] && parseFloat( value ) > 0 && ['d','e','f','g','E','G'].indexOf(type) != -1 ) {
		prefix = ' ';
	}

	if( flags['#'] && parseInt( value ) != 0 ) {
		switch(type) {
		case 'o':
			prefix = '0';
			break;
		case 'x':
		case 'X':
			prefix = '0x';
			break;
		case 'b':
			prefix = '0b';
			break;
		}
	}

	if( flags['0'] && !flags['-'] ) {
		fillchar = '0';
	}

	if( flags['width'] && flags['width'] > ( result.length + prefix.length ) ) {
		var tofill = flags['width'] - result.length - prefix.length;
		for( var i = 0; i < tofill; ++i ) {
			fill += fillchar;
		}
	}

	if( flags['-'] && !flags['0'] ) {
		result += fill;
	} else {
		result = fill + result;
	}

	return prefix + result;
};

/* </nowiki></pre> */