MediaWiki:LAPI.js

// ';      var rules = { position: 'absolute', visibility: 'hidden' ,top: 0, left: 0 ,margin: 0, border: 0 ,width: '1px', height: '1px' };     Object.merge (rules, container.style);

container.innerHTML = html; body.insertBefore(container, body.firstChild); var innerDiv = container.firstChild; var checkDiv = innerDiv.firstChild; var td = innerDiv.nextSibling.firstChild.firstChild;

data.doesNotAddBorder = (checkDiv.offsetTop !== 5); data.doesAddBorderForTableAndCells = (td.offsetTop === 5);

innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative'; data.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);

var bodyMarginTop   = body.style.marginTop; body.style.marginTop = '1px'; data.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0); body.style.marginTop = bodyMarginTop;

body.removeChild(container); };

function jQuery_offset (node) {     if (node === node.ownerDocument.body) return jQuery_bodyOffset (node); if (node.getBoundingClientRect) { var box   = node.getBoundingClientRect ; var scroll = LAPI.Pos.scrollOffset ; return {x : (box.left + scroll.x), y : (box.top + scroll.y)}; }     if (!data) jQuery_init ; var elem             = node; var offsetParent     = elem.offsetParent; var prevOffsetParent = elem; var doc              = elem.ownerDocument; var prevComputedStyle = doc.defaultView.getComputedStyle(elem, null); var computedStyle;

var top = elem.offsetTop; var left = elem.offsetLeft;

while ( (elem = elem.parentNode) && elem !== doc.body && elem !== doc.documentElement ) { computedStyle = doc.defaultView.getComputedStyle(elem, null); top -= elem.scrollTop, left -= elem.scrollLeft; if ( elem === offsetParent ) { top += elem.offsetTop, left += elem.offsetLeft; if (  data.doesNotAddBorder              && !(data.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName))             ) {           top  += parseInt (computedStyle.borderTopWidth,  10) || 0; left += parseInt (computedStyle.borderLeftWidth, 10) || 0; }         prevOffsetParent = offsetParent; offsetParent = elem.offsetParent; }       if (data.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== 'visible') {         top  += parseInt (computedStyle.borderTopWidth,  10) || 0; left += parseInt (computedStyle.borderLeftWidth, 10) || 0; }       prevComputedStyle = computedStyle; }

if (prevComputedStyle.position === 'relative' || prevComputedStyle.position === 'static') { top += doc.body.offsetTop; left += doc.body.offsetLeft; }     if (prevComputedStyle.position === 'fixed') { top += Math.max (doc.documentElement.scrollTop, doc.body.scrollTop); left += Math.max (doc.documentElement.scrollLeft, doc.body.scrollLeft); }     return {x: left, y: top}; }

function jQuery_bodyOffset (body) {     if (!data) jQuery_init; var top = body.offsetTop, left = body.offsetLeft; if (data.doesNotIncludeMarginInBodyOffset) { top += parseInt (LAPI.DOM.currentStyle (body, 'margin-top'), 10) || 0; left += parseInt (LAPI.DOM.currentStyle (body, 'margin-left'), 10) || 0; }     return {x: left, y: top}; }

return jQuery_offset; }),

isWithin : function (node, x, y) { if (!node || !node.parentNode) return false; var pos = LAPI.Pos.position (node); return   (x == null || x > pos.x && x < pos.x + node.offsetWidth) && (y == null || y > pos.y && y < pos.y + node.offsetHeight); }, // Private: // IE has some strange offset... mouse_offset : function {   if (LAPI.Browser.is_ie) { var doc_elem = document.documentElement; if (doc_elem) { if (typeof (doc_elem.getBoundingClientRect) == 'function') { var tmp = doc_elem.getBoundingClientRect ; return {x : tmp.left, y : tmp.top}; } else { return {x : doc_elem.clientLeft, y : doc_elem.clientTop}; }     }    }    return {x: 0, y : 0}; }

}; // end LAPI.Pos

} // end if (guard)

if (typeof (LAPI.Evt) == 'undefined') { LAPI.Evt = { listenTo : function (object, node, evt, f, capture) {   var listener = LAPI.Evt.makeListener (object, f); LAPI.Evt.attach (node, evt, listener, capture); }, attach : function (node, evt, f, capture) {   if (node.attachEvent) node.attachEvent ('on' + evt, f); else if (node.addEventListener) node.addEventListener (evt, f, capture); else node['on' + evt] = f; }, remove : function (node, evt, f, capture) {   if (node.detachEvent) node.detachEvent ('on' + evt, f); else if (node.removeEventListener) node.removeEventListener (evt, f, capture); else node['on' + evt] = null; }, makeListener : function (obj, listener) {   // Some hacking around to make sure 'this' is set correctly var object = obj, f = listener; return function (evt) { return f.apply (object, [evt || window.event]); } // Alternative implementation: // var f = listener.bind (obj); // return function (evt) { return f (evt || window.event); }; },

kill : function (evt) {   if (typeof (evt.preventDefault) == 'function') { evt.stopPropagation ; evt.preventDefault ; // Don't follow the link } else if (typeof (evt.cancelBubble) != 'undefined') { // IE... evt.cancelBubble = true; }   return false; // Don't follow the link (IE) }

}; // end LAPI.Evt

} // end if (guard)

if (typeof (LAPI.Edit) == 'undefined') {

LAPI.Edit = function {this.initialize.apply (this, arguments);};

LAPI.Edit.SAVE   = 1; LAPI.Edit.PREVIEW = 2; LAPI.Edit.REVERT = 4; LAPI.Edit.CANCEL = 8;

LAPI.Edit.prototype = { initialize : function (initial_text, columns, rows, labels, handlers) {   var my_labels = {box : null, preview : null, save : 'Save', cancel : 'Cancel', nullsave : null, revert : null, post: null}; if (labels) my_labels = Object.merge (labels, my_labels); this.labels = my_labels; this.timestamp = (new Date ).getTime ; this.id = 'simpleedit_' + this.timestamp; this.view = LAPI.make ('div', {id : this.id}, {marginRight: '1em'}); // Somehow, the textbox extends beyond the bounding box of the view. Don't know why, but // adding a small margin fixes the layout more or less. this.form = LAPI.make (         'form'       , { id      : this.id + '_form'           ,action  : ""           ,onsubmit: (function  {})          }      ); if (my_labels.box) { var label = LAPI.make ('div'); label.appendChild (LAPI.DOM.makeLabel (this.id + '_label', my_labels.box, this.id + '_text')); this.form.appendChild (label); }   this.textarea = LAPI.make (         'textarea'       , { id   : this.id + '_text'           ,cols : columns           ,rows : rows           ,value: (initial_text ? initial_text.toString : "")          }      ); LAPI.Evt.attach (this.textarea, 'keyup', LAPI.Evt.makeListener (this, this.text_changed)); // Catch cut/copy/paste through the context menu. Some browsers support oncut, oncopy, // onpaste events for this, but since that's only IE, FF 3, Safari 3, and Chrome, we   // cannot rely on this. Instead, we check again as soon as we leave the textarea. Only // minor catch is that on FF 3, the next focus target is determined before the blur event // fires. Since in practice save will always be enabled, this shouldn't be a problem. LAPI.Evt.attach (this.textarea, 'mouseout', LAPI.Evt.makeListener (this, this.text_changed)); LAPI.Evt.attach (this.textarea, 'blur', LAPI.Evt.makeListener (this, this.text_changed)); this.form.appendChild (this.textarea); this.form.appendChild (LAPI.make ('br')); this.preview_section = LAPI.make ('div', null, {borderBottom: '1px solid #8888aa', display: 'none'}); this.view.insertBefore (this.preview_section, this.view.firstChild); this.save = LAPI.DOM.makeButton (this.id + '_save', my_labels.save, LAPI.Evt.makeListener (this, this.do_save)); this.form.appendChild (this.save); if (my_labels.preview) { this.preview = LAPI.DOM.makeButton (this.id + '_preview', my_labels.preview, LAPI.Evt.makeListener (this, this.do_preview)); this.form.appendChild (this.preview); }   this.cancel = LAPI.DOM.makeButton (this.id + '_cancel', my_labels.cancel, LAPI.Evt.makeListener (this, this.do_cancel)); this.form.appendChild (this.cancel); this.view.appendChild (this.form); if (my_labels.post) { this.post_text = LAPI.DOM.setContent (LAPI.make ('div'), my_labels.post); this.view.appendChild (this.post_text); }   if (handlers) Object.merge (handlers, this); if (typeof (this.ongettext) != 'function') this.ongettext = function (text) { return text;}; // Default: no modifications this.current_mask = LAPI.Edit.SAVE + LAPI.Edit.PREVIEW + LAPI.Edit.REVERT + LAPI.Edit.CANCEL; if ((!initial_text || initial_text.trim .length == 0) && this.preview) this.preview.disabled = true; if (my_labels.revert) { this.revert = LAPI.DOM.makeButton (this.id + '_revert', my_labels.revert, LAPI.Evt.makeListener (this, this.do_revert)); this.form.insertBefore (this.revert, this.cancel); }   this.original_text = ""; }, getView : function {   return this.view; }, getText : function {   return this.ongettext (this.textarea.value); }, setText : function (text) {   this.textarea.value = text; this.original_text = text; this.text_changed ; }, changeText : function (text) {   this.textarea.value = text; this.text_changed ; },

hidePreview : function {   this.preview_section.style.display = 'none'; if (this.onpreview) this.onpreview (this); },

showPreview : function {   this.preview_section.style.display = ""; if (this.onpreview) this.onpreview (this); },

setPreview : function (html) {   if (html.nodeName) { LAPI.DOM.removeChildren (this.preview_section); this.preview_section.appendChild (html); } else { this.preview_section.innerHTML = html; } },  busy : function (show) {   if (show) LAPI.Ajax.injectSpinner (this.cancel, this.id + '_spinner'); else LAPI.Ajax.removeSpinner (this.id + '_spinner'); }, do_save : function (evt) {   if (this.onsave) this.onsave (this); return true; }, do_revert : function (evt) {   this.changeText (this.original_text); return true; },

do_cancel : function (evt) {   if (this.oncancel) this.oncancel (this); return true; }, do_preview : function (evt) {   var self = this; this.busy (true); LAPI.Ajax.parseWikitext (       this.getText      , function (text, failureFunc)        {          self.busy (false);          self.setPreview (text);          self.showPreview ;        }      , function (req, json_result)        {          // Error. TODO: user feedback?          self.busy (false);        }      , true      , mw.config.get('wgUserLanguage') || null      , mw.config.get('wgPageName') || null    ); return true; },

enable : function (bit_set) {   var call_text_changed = false; this.current_mask = bit_set; this.save.disabled = ((bit_set & LAPI.Edit.SAVE) == 0); this.cancel.disabled = ((bit_set & LAPI.Edit.CANCEL) == 0); if (this.preview) { if ((bit_set & LAPI.Edit.PREVIEW) == 0) this.preview.disabled = true; else call_text_changed = true; }   if (this.revert) { if ((bit_set & LAPI.Edit.REVERT) == 0) this.revert.disabled = true; else call_text_changed = true; }   if (call_text_changed) this.text_changed ; },

text_changed : function (evt) {   var text = this.textarea.value; text = text.trim ; var length = text.length; if (this.preview && (this.current_mask & LAPI.Edit.PREVIEW) != 0) { // Preview is basically enabled this.preview.disabled = (length <= 0); }   if (this.labels.nullsave) { if (length > 0) { this.save.value = this.labels.save; } else { this.save.value = this.labels.nullsave; }   }    if (this.revert) { this.revert.disabled = (text == this.original_text || this.textarea.value == this.original_text); }   return true; }

}; // end LAPI.Edit

} // end if (guard)

//