MediaWiki:Gadget-ImageAnnotator.js: Difference between revisions

Content deleted Content added
rv, not sure why - but it is broken...
Line 2:
 
/*
ImageAnnotator v3v2.03.0-alpha2
 
ATTENTION:
Requires an environment running MediaWiki 1.23 or later.
This is in the Gadget- prefix but not actually registered nor loaded as a Gadget. It is
loaded directly by [[MediaWiki:Common.js]], raw, unminified and in the global scope.
 
Image annotations. Draw rectangles onto image thumbnail displayed on image description
Line 23 ⟶ 25:
See http://commons.wikimedia.org/wiki/Help:Gadget-ImageAnnotator for documentation.
*/
/*jshint eqeqeq:true, undef:true, unused:true, browser:true, eqnull:true, laxbreak:true, laxcomma:true */
/*global prompt */ // discouraged browser globals
/*global wgServer, wgContentLanguage */ // legacy config site globals
/*global wgUserGroups, wgRestrictionEdit */ // legacy config page globals
/*global mw, $ */
/*global importScript, importScriptURI */ // wikibits.js
/*global LAPI, Buttons, Tooltip, Tooltips, TextCleaner, UIElements */ // imported scripts
/*global ImageAnnotator:true, ImageAnnotator_disable:false */ // this script
 
// Global: importScript, importScriptURI (wiki.js)
// Guard against multiple inclusions
// Global: wgPageName, wgCurRevisionId, wgUserGroups, wgRestrictionEdit (inline script on the page)
if (typeof ImageAnnotator === 'undefined') {
// Global: wgAction, wgNamespaceNumber, wgUserLanguage, wgContentLanguage, stylepath (inline script)
// Global: wgNamespaceIds (inline script)
/*jshint eqnull:true, laxbreak:true, laxcomma:true */
 
if (typeof ImageAnnotator === 'undefined') { // Guard against multiple inclusions
 
importScript('MediaWiki:LAPI.js');
Line 40 ⟶ 39:
importScript('MediaWiki:UIElements.js');
 
(function () { // Local scope
(function () {
 
var conf = mw.config.get([
'skin',
'stylepath',
'wgArticleId',
'wgCurRevisionId',
'wgNamespaceIds',
'wgNamespaceNumber',
'wgPageName',
'wgTitle',
'wgUserLanguage'
]);
 
var ImageAnnotator_config = null;
 
var ImageAnnotation = function () { this.initialize.apply(this, arguments); };
this.initialize.apply(this, arguments);
};
 
ImageAnnotation.compare = function (a, b)
{
var result = b.area() - a.area();
if (result !== 0) return result;
return a.model.id - b.model.id; // Just to make sure the order is complete
return a.model.id - b.model.id;
};
 
ImageAnnotation.prototype =
{
view : null, // Rectangle to be displayed on image: a div with pos and size
model : null, // Internal representation of the annotation
view: null,
tooltip : null, // InternalTooltip representationto ofdisplay the annotation
modelcontent : null, // Content of the tooltip
viewer : null, // Reference to the viewer this note belongs to
// Tooltip to display the annotation
tooltip: null,
// Content of the tooltip
content: null,
// Reference to the viewer this note belongs to
viewer: null,
 
initialize : function (node, viewer, id)
{
var is_new = false;
var view_w = 0, view_h = 0, view_x = 0, view_y = 0;
Line 92 ⟶ 73:
var html = IA.getRawItem('content_' + id, viewer.scope);
if (x === null || y === null || w === null || h === null || html === null)
throw new Error ('Invalid note');
if (x < 0 || x >= viewer.full_img.width || y < 0 || y >= viewer.full_img.height)
throw new Error ('Invalid note: origin invalid on note ' + id);
if ( x + w > viewer.full_img.width + 10
|| y + h > viewer.full_img.height + 10)
) {
throw new Error ('Invalid note: size extends beyond image on note ' + id);
}
// Notes written by early versions may be slightly too large, whence the + 10 above. Fix this.
Line 108 ⟶ 89:
view_y = Math.floor(y / viewer.factors.dy);
this.view =
LAPI.make(
'div', null, {
, { position : 'absolute',
,display : 'none',
,lineHeight : '0px' // IE
lineHeight ,fontSize : '0px', // IE
// IE ,top : '' + view_y + 'px'
fontSize ,left : '0px', + view_x + 'px'
top ,width : '' + view_yview_w + 'px',
left ,height : '' + view_xview_h + 'px',
width: '' + view_w + 'px',}
height: '' + view_h + 'px');
});
// We'll add the view to the DOM once we've loaded all notes
this.model = {
{ id : id,
,dimension: { x: x, y: y, w: w, h: h },
,wiki : '',
,html : html.cloneNode(true)
};
} else {
is_new = true;
this.view = node;
this.model = {
{ id : -1,
,dimension: null,
,wiki : '',
,html : null
};
view_w = this.view.offsetWidth - 2; // Subtract cumulated border widths
view_h = this.view.offsetHeight - 2;
Line 146 ⟶ 127:
if (view_h < 6) {view_y = Math.floor(view_y + view_h / 2 - 3); view_h = 6; }
Object.merge(
{ left: '' + view_x + 'px', top: '' + view_y + 'px'
{
left ,width: '' + view_xview_w + 'px', height: '' + view_h + 'px'}
, top: '' + view_y + 'px',this.view.style
width: '' + view_w + 'px',
height: '' + view_h + 'px'
},
this.view.style
);
this.view.style.zIndex = 500; // Below tooltips
try {
this.view.style.border = '1px solid ' + this.viewer.outer_border;
Line 164 ⟶ 141:
'div', null
, { lineHeight : '0px' // IE
,fontSize : '0px' // IE
,width : '' + Math.max(view_w - 2, 0) + 'px' // -2 to leave space for the border
,height : '' + Math.max(view_h - 2, 0) + 'px'
}
)
Line 189 ⟶ 166:
},
 
setTooltip : function ()
{
if (this.tooltip || !this.view) return; // Already set, or corrupt
// Note: on IE, don't have tooltips appear automatically. IE doesn't do it right for transparent
// targets and we have to show and hide them ourselves through a mousemove listener in the viewer
// anyway. The occasional event that IE sends to the tooltip may then lead to ugly flickering.
this.tooltip = new Tooltip(
( this.view.firstChild,
, this.display.bind(this),
, { activate : (LAPI.DOM.is_ie ? Tooltip.NONE : Tooltip.HOVER)
{
activate ,deactivate : (LAPI.DOM.is_ie ? Tooltip.NONEESCAPE : Tooltip.HOVERLEAVE),
,close_button : null
deactivate: (LAPI.DOM.is_ie ? Tooltip.ESCAPE : Tooltip.LEAVE),
,mode : Tooltip.MOUSE
close_button: null,
,mouse_offset : {x: -5, y: -5, dx: (IA.is_rtl ? -1 : 1), dy: 1}
mode: Tooltip.MOUSE,
,open_delay : 0
mouse_offset: { x: -5, y: -5, dx: (IA.is_rtl ? -1 : 1), dy: 1 },
open_delay ,hide_delay : 0,
hide_delay ,onclose : 0(function (tooltip, evt) {
if (this.view) {
onclose: (function (tooltip, evt) {
if (this.view) try {
this.view.style.border = '1px solid ' + this.viewer.outer_border;
try {
this.view.style.border = '1px solid ' + this.viewer.outer_border; } catch (ex) {
this.view.style.border = '1px solid ' + IA.outer_border;
} catch (ex) {
this.view.style.border = '1px solid ' + IA.outer_border; }
}
if (this.viewer.tip == tooltip) this.viewer.tip = null;
}
// Hide all boxes if we're outside the image. Relies on hide checking the
if (this.viewer.tip === tooltip) this.viewer.tip = null;
// coordinates! (Otherwise, we'd always hide...)
// Hide all boxes if we're outside the image. Relies on hide checking the
if (evt) this.viewer.hide(evt);
// coordinates! (Otherwise, we'd always hide...)
if (evt) this }).viewer.hidebind(evtthis);
}).bind ,onopen : (thisfunction (tooltip), {
onopen: (function if (tooltipthis.view) {
if (this.view) try {
this.view.style.border = '1px solid ' + this.viewer.active_border;
try {
this.view.style.border = '1px solid ' + this.viewer.active_border; } catch (ex) {
this.view.style.border = '1px solid ' + IA.active_border;
} catch (ex) {
this.view.style.border = '1px solid ' + IA.active_border; }
}
this.viewer.tip = tooltip;
}
this.viewer.tip = tooltip; }).bind(this)
}).bind(this)
} , IA.tooltip_styles
IA.tooltip_styles);
);
},
 
display : function (evt)
{
if (!this.content) {
this.content = LAPI.make('div');
Line 243 ⟶ 221:
this.content.appendChild(LAPI.make('div', null, { clear: 'both' }));
if (this.viewer.may_edit) {
this.content.button_section =
LAPI.make(
'div',
null ,null
,{ fontSize : 'smaller'
fontSize ,textAlign: (IA.is_rtl ? 'smallerleft', : 'right')
textAlign: (IA.is_rtl ? 'left' ,borderTop: 'right'),IA.tooltip_styles.border
borderTop: IA.tooltip_styles.border }
} );
);
this.content.appendChild(this.content.button_section);
this.content.button_section.appendChild(LAPI.DOM.makeLink(
'#',
, ImageAnnotator.UI.get('wpImageAnnotatorEdit', true),
null , null
, LAPI.Evt.makeListener(this, this.edit)
));
);
if (ImageAnnotator_config.mayDelete()) {
this.content.button_section.appendChild(document.createTextNode('\xa0'));
this.content.button_section.appendChild
(LAPI.DOM.makeLink(
( '#',
, ImageAnnotator.UI.get('wpImageAnnotatorDelete', true),
null , null
, LAPI.Evt.makeListener(this, this.remove_event)
));
);
}
}
Line 273 ⟶ 254:
},
 
edit : function (evt)
{
if (IA.canEdit()) IA.editor.editNote(this);
if (evt) return LAPI.Evt.kill(evt);
Line 279 ⟶ 261:
},
 
remove_event : function (evt)
{
if (IA.canEdit()) this.remove();
return LAPI.Evt.kill(evt);
},
 
remove : function ()
{
if (!this.content) { // New note: just destroy it.
this.destroy();
Line 313 ⟶ 297:
if (this.tooltip) this.tooltip.size_change();
LAPI.Ajax.editPage(
conf.wgPageName
, function (doc, editForm, failureFunc, revision_id) {
{
try {
if (revision_id && revision_id !== conf.wgCurRevisionId)
throw new Error ('#Page version (revision ID) mismatch: edit conflict.');
 
var textbox = editForm.wpTextbox1;
if (!textbox) throw new Error ('#Server replied with invalid edit page.');
var pagetext = textbox.value.replace(/\r\n/g, '\n');
// Normalize different end-of-line handling. Opera and IE may use \r\n, whereas other
Line 337 ⟶ 322:
}
var char_before = 0;
var char_after = 0;
if (span.start > 0) char_before = pagetext.charCodeAt(span.start - 1);
if (span.end < pagetext.length) char_after = pagetext.charCodeAt(span.end);
if ( String.fromCharCode(char_before) === '\n'
&& String.fromCharCode(char_after) = == '\n')
span.start = span.start - 1;
pagetext = pagetext.substring(0, span.start) + pagetext.substring(span.end);
Line 355 ⟶ 340:
);
} catch (ex) {
failureFuncfailure (null, ex);
return;
}
Line 362 ⟶ 347:
editForm
, function (request) {
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
edit_page.dispose();
var revision_id = LAPI.WP.revisionFromHtml(request.responseText);
if (!revision_id) {
failureFunc (request, new Error ('Revision ID not found. Please reload the page.'));
return;
}
conf.wgCurRevisionId = revision_id; // Bump revision id!!
LAPI.Ajax.removeSpinner(spinnerId);
if (self.tooltip) self.tooltip.size_change();
Line 375 ⟶ 360:
}
, function (request, ex) {
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
edit_page.dispose();
failureFunc (request, ex);
}
);
}
, function (request, ex) {
// Failure. What now? TODO: Implement some kind of user feedback.
LAPI.Ajax.removeSpinner(spinnerId);
Line 391 ⟶ 376:
},
 
destroy : function ()
{
if (this.view) LAPI.DOM.removeNode(this.view);
if (this.dummy) LAPI.DOM.removeNode(this.dummy);
if (this.tooltip) this.tooltip.hide_now();
if (this.model && this.model.id > 0 && this.viewer) this.viewer.deregister(this);
this.model = null;
this.view = null;
this.content = null;
this.tooltip = null;
this.viewer = null;
},
 
area : function ()
{
if (!this.model || !this.model.dimension) return 0;
return (this.model.dimension.w * this.model.dimension.h);
},
 
cannotEdit : function ()
{
if (this.content && this.content.button_section) {
LAPI.DOM.removeNode(this.content.button_section);
Line 422 ⟶ 410:
ImageAnnotationEditor.prototype =
{
initialize : function ()
{
var editor_width = 50;
// Respect potential user-defined width setting
if ( window.ImageAnnotationEditor_columns
&& !isNaN (window.ImageAnnotationEditor_columns)
&& window.ImageAnnotationEditor_columns >= 30
Line 434 ⟶ 423:
new LAPI.Edit(
'' , editor_width, 6
, { box : ImageAnnotator.UI.get('wpImageAnnotatorEditorLabel', false)
,preview : ImageAnnotator.UI.get('wpImageAnnotatorPreview', true).capitalizeFirst()
,save : ImageAnnotator.UI.get('wpImageAnnotatorSave', true).capitalizeFirst()
,revert : ImageAnnotator.UI.get('wpImageAnnotatorRevert', true).capitalizeFirst()
,cancel : ImageAnnotator.UI.get('wpImageAnnotatorCancel', true).capitalizeFirst()
,nullsave : ImageAnnotator_config.mayDelete()
? ImageAnnotator.UI.get('wpImageAnnotatorDelete', true).capitalizeFirst()
: null
,post : ImageAnnotator.UI.get('wpImageAnnotatorCopyright', false)
}
, {
onsave : this.save.bind(this)
,onpreview : this.onpreview.bind(this)
,oncancel : this.cancel.bind(this)
,ongettext : function (text) {
if (text == null) return '';
text = text.trim()
Line 488 ⟶ 477:
IA.get_cover()
, this.get_editor.bind(this)
, { activate : Tooltip.NONE // We'll always show it explicitly
,deactivate : Tooltip.ESCAPE
,close_button : null // We have a cancel button anyway
,mode : Tooltip.FIXED
,anchor : Tooltip.TOP_LEFT
,mouse_offset : { x:10, y: 10, dx: 1, dy: 1 } // Misuse this: fixed offset from view
,max_pixels : (box_width ? box_width + 20 : 0) // + 20 gives some slack
,z_index : 2010 // Above the cover.
,open_delay : 0
,hide_delay : 0
,onclose : this.close_tooltip.bind(this)
}
, IA.tooltip_styles
Line 505 ⟶ 494:
this.visible = false;
LAPI.Evt.listenTo(this, this.tooltip.popup, IA.mouse_in,
function (evt) {
Array.forEach(IA.viewers, (function (viewer) {
if (viewer !== this.viewer && viewer.visible) viewer.hide();
}).bind(this));
}
Line 513 ⟶ 502:
},
 
get_editor : function () {
return this.box;
},
 
editNote : function (note)
{
var same_note = (note === this.note);
this.note = note;
this.viewer = this.note.viewer;
 
var cover = IA.get_cover();
cover.style.cursor = 'auto';
IA.show_cover();
Line 532 ⟶ 522:
// Existing note, and we don't have the wikitext yet: go get it
var self = this;
LAPI.Ajax.apiGet('query',
{ 'query'
, { prop : 'revisions',
,titles : conf.wgPageName,
,rvlimit : 1,
,rvstartid : conf.wgCurRevisionId,
,rvprop : 'ids|content'
},
, function (request, json_result) {
if (json_result && json_result.query && json_result.query.pages) {
// Should have only one page here
for (var page in json_result.query.pages) {
var p = json_result.query.pages[page];
if (p && p.revisions && p.revisions.length) {
var rev = p.revisions[0];
if (rev.revid === conf.wgCurRevisionId && rev["*"] && rev["*"].length)
IA.setWikitext(rev["*"]);
}
break;
}
break;
}
// TODO: What upon a failure?
self.open_editor(same_note, cover);
}
, function (request) {
// TODO: What upon a failure?
self.open_editor(same_note, cover);
}
// TODO: What upon a failure?
self.open_editor(same_note, cover);
},
function () {
// TODO: What upon a failure?
self.open_editor(same_note, cover);
}
);
} else {
Line 566 ⟶ 556:
},
 
open_editor : function (same_note, cover)
{
this.editor.hidePreview();
if (!same_note || this.editor.textarea.readOnly)
Line 577 ⟶ 568:
// Set the position relative to the note's view.
var view_pos = LAPI.Pos.position(this.note.view);
var origin = LAPI.Pos.position(cover);
this.tooltip.options.fixed_offset.x =
view_pos.x - origin.x + this.tooltip.options.mouse_offset.x;
Line 604 ⟶ 595:
},
 
hide_editor : function (evt)
{
if (!this.visible) return;
this.visible = false;
IA.is_editing = false;
this.tooltip.hide_now(evt);
if (evt && evt.type === 'keydown' && !this.saving) {
// ESC pressed on new note before a save attempt
this.cancel();
Line 624 ⟶ 616:
},
 
save : function (editor)
{
var data = editor.getText();
if (!data || !data.length) {
Line 637 ⟶ 630:
}
return;
} else if (data === this.note.model.wiki) {
// Text unchanged
this.hide_editor();
Line 647 ⟶ 640:
if (!dim) {
dim = {
x : Math.round(this.dim.x * this.viewer.factors.dx),
y : Math.round(this.dim.y * this.viewer.factors.dy),
w : Math.round(this.dim.w * this.viewer.factors.dx),
h : Math.round(this.dim.h * this.viewer.factors.dy)
};
// Make sure everything is within bounds
Line 708 ⟶ 701:
this.editor.enable(0); // Disable all buttons
this.saving = true;
LAPI.Ajax.editPage(conf.
wgPageName,
, function (doc, editForm, failureFunc, revision_id) {
{
try {
if (revision_id && revision_id !== conf.wgCurRevisionId)
// Page was edited since the user loaded it.
throw new Error('#Page version (revision ID) mismatch: edit conflict.');
Line 772 ⟶ 767:
}
} catch (ex) {
failureFunc (null, ex);
return;
}
var edit_page = doc;
LAPI.Ajax.submitEdit(
editForm
, function (request) {
// After a successful submit.
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
Line 782 ⟶ 779:
// TODO: Actually, the edit got through here, so calling failureFunc on
// inconsistencies isn't quite right. Should we reload the page?
var id = 'image_annotation_content_' + self.note.model.id;
var doc = LAPI.Ajax.getHTML(request, failureFunc, id);
if (!doc) return;
var html = LAPI.$ (id, doc);
Line 789 ⟶ 786:
if (doc.isFake && (typeof doc.dispose === 'function')) doc.dispose();
failureFunc
(request, new Error ('#Note not found after saving. Please reload the page.'));
return;
}
Line 796 ⟶ 793:
if (doc.isFake && (typeof doc.dispose === 'function')) doc.dispose();
failureFunc
(request, new Error ('#Version inconsistency after saving. Please reload the page.'));
return;
}
conf.wgCurRevisionId = revision_id; // Bump revision id!!
self.note.model.html = LAPI.DOM.importNode(document, html, true);
if (doc.isFake && (typeof doc.dispose === 'function')) doc.dispose();
Line 826 ⟶ 823:
IA.is_editing = false;
self.editor.setText(data); // In case the same note is re-opened: start new undo cycle
},
, function (request, ex) {
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
edit_page.dispose();
failureFunc (request, ex);
}
);
},
, function (request, ex) {
{
self.editor.busy(false);
self.saving = false;
Line 844 ⟶ 842:
// Error message. Use preview field for this.
var error_msg = ImageAnnotator.UI.get('wpImageAnnotatorSaveError', false);
var lk = error_msg.getElementsByClassName(error_msg, 'span', 'wpImageAnnotatorOwnPageLink');
if (lk && lk.length && lk[0].firstChild.nodeName.toLowerCase() === 'a') {
lk = lk[0].firstChild;
lk.href = mw.utilwgServer + wgArticlePath.getUrlreplace(conf.wgPageName'$1', {encodeURIComponent(wgPageName)) action:+ '?action=edit' });
}
if (ex) {
Line 865 ⟶ 863:
self.editor.textarea.style.backgroundColor = '#EEEEEE';
self.editor.enable(LAPI.Edit.CANCEL); // Disable all other buttons
}
);
},
 
onpreview : function (editor)
{
if (this.tooltip) this.tooltip.size_change();
},
 
cancel : function (editor)
{
if (!this.note) return;
if (!this.note.content) {
Line 883:
},
 
close_tooltip : function (tooltip, evt)
{
this.hide_editor(evt);
this.cancel();
Line 894 ⟶ 895:
ImageNotesViewer.prototype =
{
initialize : function (descriptor, may_edit)
{
Object.merge(descriptor, this);
this.annotations = [];
this.max_id = 0;
this.main_div = null;
this.msg = null;
this.may_edit = may_edit;
this.setup_done = false;
this.tip = null;
this.icon = null;
this.factors = {
{ dx : this.full_img.width / this.thumb.width,
,dy : this.full_img.height / this.thumb.height
};
 
if (!this.isThumbnail && !this.isOther) {
Line 922 ⟶ 924:
},
 
setup : function (onlyIcon)
{
this.setup_done = true;
var name = this.realName;
if (this.isThumbnail || this.scope == document || this.may_edit || !IA.haveAjax) {
var $fullname;
if ( this.isThumbnailimgName || this.scope === document || this.may_edit || !IA.haveAjax) {realName;
this.imgName = this.realName;
this.realName = '';
} else {
$fullnamename = $getElementsByClassName (this.scope).find(, '*', '.wpImageAnnotatorFullName');
this.realName = $fullname((name && name.length) ? $fullnameLAPI.textDOM.getInnerText(name[0]) : '');
this.imgName = this.realName;
}
 
var annotations = getElementsByClassName (this.scope.getElementsByClassName(, 'div', IA.annotation_class);
 
if (!this.may_edit && (!annotations || annotations.length === 0))
Line 947 ⟶ 949:
LAPI.make('div', null, {position: 'relative', width: '' + this.thumb.width + 'px'});
var floater =
LAPI.make(
'div', null, {
, { cssFloat : (IA.is_rtl ? 'right' : 'left'),
,styleFloat: (IA.is_rtl ? 'right' : 'left') // For IE...
styleFloat: (IA.is_rtl ? ,width : 'right' :+ this.thumb.width + 'leftpx'),
width ,position : 'relative' +// this.thumb.widthFixes +IE 'px',layout bugs...
// Fixes IE layout bugs...}
position: 'relative');
});
floater.appendChild(this.img_div);
this.img.parentNode.parentNode.insertBefore(floater, this.img.parentNode);
Line 962 ⟶ 964:
LAPI.DOM.insertAfter(breaker, floater);
// Remove spurious br tag.
if (breaker.nextSibling && breaker.nextSibling.nodeName.toLowerCase() === 'br')
LAPI.DOM.removeNode(breaker.nextSibling);
} else if (this.isOther || isEnabledImage) {
this.img_div =
LAPI.make('div', null, { position: 'relative', width: '' + this.thumb.width + 'px' });
this.img.parentNode.parentNode.insertBefore(this.img_div, this.img.parentNode);
this.img_div.appendChild(this.img.parentNode);
// Insert one more to have a file_div, so that we can align the message text correctly
this.file_div = LAPI.make('div', null, { width: '' + this.thumb.width + 'px' });
this.img_div.parentNode.insertBefore(this.file_div, this.img_div);
this.file_div.appendChild(this.img_div);
} else { // Thumbnail
this.img_div =
LAPI.make('div',
{ 'div'
, {className: 'thumbimage' },
, {position: 'relative', width: '' + this.thumb.width + 'px'}
{
position: 'relative',
width: '' + this.thumb.width + 'px'
}
);
this.img.parentNode.parentNode.insertBefore(this.img_div, this.img.parentNode);
Line 986:
this.img_div.appendChild(this.img.parentNode);
}
if ( (this.isThumbnail || this.isOther) && !this.may_edit
&& ( onlyIcon
|| this.iconOnly
|| ImageAnnotator_config.inlineImageUsesIndicator
(name, this.isLocal, this.thumb, this.full_img, annotations.length, this.isThumbnail)
)
) { )
{
// Use an onclick handler instead of a link around the image. The link may have a default white
// background, but we want to be sure to have transparency. The image should be an 8-bit indexed
Line 999 ⟶ 1,000:
if (this.icon) this.icon = this.icon.firstChild; // Skip the message container span or div
// Guard against misconfigurations
if ( this.icon
&& this.icon.nodeName.toLowerCase() === 'a'
&& this.icon.firstChild.nodeName.toLowerCase() === 'img'
) { )
{
// Make sure we use the right protocol:
var srcFixed = this.icon.firstChild.getAttribute('src', 2).replace(/^https?\:/, document.___location.protocol);
Line 1,017 ⟶ 1,019:
}
Object.merge(
{ {position: 'absolute', zIndex: 1000, top: '0px', cursor: 'pointer' },
, this.icon.style
);
this.icon.onclick = (function () { ___location.href = this.img.parentNode.href; }).bind(this);
Line 1,031 ⟶ 1,033:
// Set colors
var colors = IA.getRawItem('colors', this.scope);
this.outer_border =
colors && IA.getItem('outer', colors) || IA.outer_border;
this.inner_border =
colors && IA.getItem('inner', colors) || IA.inner_border;
this.active_border =
colors && IA.getItem('active', colors) || IA.active_border;
Line 1,039 ⟶ 1,043:
var id = annotations[i].id;
if (id && /^image_annotation_note_(\d+)$/.test(id)) {
id = parseInt (id.substring('image_annotation_note_'.length));
} else
id = null;
Line 1,046 ⟶ 1,050:
var w = IA.getIntItem('full_width_' + id, this.scope);
var h = IA.getIntItem('full_height_' + id, this.scope);
if ( w === this.full_img.width && h === this.full_img.height
&& !Array.exists(this.annotations, function (note) { return note.model.id === id; })
) { )
{
try {
this.register(new ImageAnnotation(annotations[i], this, id));
Line 1,062 ⟶ 1,067:
Array.forEach(this.annotations, (function (note) {this.img_div.appendChild(note.view);}).bind(this));
if (this.isThumbnail) {
this.main_div = getElementsByClassName (this.file_div.getElementsByClassName(, 'div', 'thumbcaption');
if (!this.main_div || this.main_div.length === 0)
this.main_div = null;
else
Line 1,083 ⟶ 1,088:
}
}
if ( !(this.isThumbnail || this.isOther)
|| !this.noCaption
&& !IA.hideCaptions
&& ImageAnnotator_config.displayCaptionInArticles
(name, this.isLocal, this.thumb, this.full_img, annotations.length, this.isThumbnail)
) { )
{
this.msg = LAPI.make('div', null, { display: 'none' });
this.msg = LAPI.make('div', null, {display: 'none'});
if (IA.is_rtl) {
this.msg.style.direction = 'rtl';
Line 1,100 ⟶ 1,106:
// Set overflow parents, if any
 
var simple = !!window.getComputedStyle;
var checks = (simple ? ['overflow', 'overflow-x', 'overflow-y']
: ['overflow', 'overflowX', 'overflowY']
);
var curStyle = null;
for (var up = this.img.parentNode.parentNode; up !== document.body; up = up.parentNode) {
curStyle = (simple ? window.getComputedStyle(up, null) : (up.currentStyle || up.style));
// "up.style" is actually incorrect, but a best-effort fallback.
Line 1,111 ⟶ 1,117:
Array.any(checks, function (t) {
var o = curStyle[t];
return (o && o !== 'visible') ? o : null;
});
if (overflow) {
Line 1,145 ⟶ 1,151:
},
 
cannotEdit : function ()
{
if (!this.may_edit) return;
this.may_edit = false;
Line 1,151 ⟶ 1,158:
},
 
setShowHideEvents : function (set)
{
if (this.icon) return;
if (set) {
Line 1,165 ⟶ 1,173:
},
 
removeMoveListener : function ()
{
if (this.icon) return;
this.move_listening = false;
Line 1,175 ⟶ 1,184:
},
 
adjustRectangleSize : function (node)
{
if (this.icon) return;
// Make sure the note boxes don't overlap the image boundary; we might get an event
Line 1,194 ⟶ 1,204:
}
// Now set position and width and height, subtracting cumulated border widths
if ( view_x !== node.offsetLeft || view_y !== node.offsetTop
|| view_w !== node.offsetWidth || view_h !== node.offsetHeight)
) {
node.style.top = '' + view_y + 'px';
node.style.left = '' + view_x + 'px';
Line 1,206 ⟶ 1,216:
},
 
toggle : function (dummies)
{
var i;
if (!this.annotations || this.annotations.length === 0 || this.icon) return;
Line 1,229 ⟶ 1,240:
},
 
show : function (evt)
{
if (this.visible || this.icon) return;
this.toggle(IA.is_adding || IA.is_editing);
Line 1,240 ⟶ 1,252:
},
 
hide : function (evt)
{
function intersect_rectangles(a, b) {
if (b.x > a.r || b.r < a.x || b.y > a.b || b.b < a.y)
return { x:0, y:0, r:0, b:0 };
 
return {
x: Math.max(a.x, b.x),
y: Math.max(a.y, b.y),
r: Math.min(a.r, b.r),
b: Math.min(a.b, b.b)
};
}
if (this.icon) return true;
if (!this.visible) {
Line 1,278 ⟶ 1,280:
// Compute the actually visible region by intersecting the rectangle given by img_pos and
// this.img.offsetWidth, this.img.offsetTop with the rectangles of all overflow parents.
 
function intersect_rectangles (a, b) {
if (b.x > a.r || b.r < a.x || b.y > a.b || b.b < a.y)
return { x:0, y:0, r:0, b:0 };
 
return {
x: Math.max(a.x, b.x),
y: Math.max(a.y, b.y),
r: Math.min(a.r, b.r),
b: Math.min(a.b, b.b)
};
}
 
for (i = 0; i < this.overflowParents.length && rect.x < rect.r && rect.y < rect.b; i++) {
img_pos = LAPI.Pos.position(this.overflowParents[i]);
img_pos.r = img_pos.x + this.overflowParents[i].clientWidth;
img_pos.b = img_pos.y + this.overflowParents[i].clientHeight;
rect = intersect_rectangles (rect, img_pos);
}
}
 
is_within = !( rect.x >= rect.r || rect.y >= rect.b // Empty rectangle
|| rect.x >= mouse_pos.x || rect.r <= mouse_pos.x
|| rect.y >= mouse_pos.y || rect.b <= mouse_pos.y
);
if (is_within) {
Line 1,297 ⟶ 1,311:
for (i = this.annotations.length - 1; i >= 0; i--) {
display = this.annotations[i].view.style.display;
if ( display !== 'none' && display != null
&& LAPI.Pos.isWithin(this.annotations[i].view.firstChild, mouse_pos.x, mouse_pos.y)
) {
{
if (!this.annotations[i].tooltip.visible) this.annotations[i].tooltip.show(evt);
return true;
Line 1,316 ⟶ 1,331:
},
 
check_hide : function (evt)
{
if (this.icon) return true;
if (this.visible)
Line 1,323 ⟶ 1,339:
},
 
register : function (new_note)
{
this.annotations[this.annotations.length] = new_note;
if (new_note.model.id > 0) {
Line 1,332 ⟶ 1,349:
},
 
deregister : function (note)
{
Array.remove(this.annotations, note);
if (note.model.id === this.max_id) this.max_id--;
if (this.annotations.length === 0) this.setDefaultMsg(); //If we removed the last one, clear the msg
},
 
setDefaultMsg : function ()
{
if (this.annotations && this.annotations.length && this.msg) {
LAPI.DOM.removeChildren(this.msg);
Line 1,351 ⟶ 1,370:
lk.parentNode.replaceChild(
LAPI.DOM.makeLink(
mwwgArticlePath.util.getUrlreplace('$1', encodeURIComponent(this.realName))
, this.realName
, this.realName
Line 1,370 ⟶ 1,389:
};
 
var IA =
{
// This object is responsible for setting up annotations when a page is loaded. It loads all
// annotations in the page source, and adds an "Annotate this image" button plus the support
// for drawing rectangles onto the image if there is only one image and editing is allowed.
 
haveAjax : false,
 
button_div : null,
add_button : null,
 
cover : null,
border : null,
definer : null,
 
mouse_in : (window.ActiveXObject ? 'mouseenter' : 'mouseover'),
mouse_out : (window.ActiveXObject ? 'mouseleave' : 'mouseout'),
 
annotation_class : 'image_annotation',
 
// Format of notes in Wikitext. Note: there are two formats, an old one and a new one.
Line 1,393 ⟶ 1,413:
// important, because the old format also used the ImageNote template, but for a different
// purpose.
note_delim :
[
{ start : '<div id="image_annotation_note_$1"'
,end : '</div><!-- End of annotation $1-->'
,content_start : '<div id="image_annotation_content_$1">\n'
,content_end : '</div>\n<span id="image_annotation_wikitext_$1"'
}
,{ start : '{{ImageNote|id=$1'
,end : '{{ImageNoteEnd|id=$1}}'
,content_start : '}}\n'
,content_end : '{{ImageNoteEnd|id=$1}}'
}
],
 
tooltip_styles : // The style for all our tooltips
{ border : '1px solid #8888aa'
tooltip_styles: {
, backgroundColor border: '1px solid #8888aaffffe0'
, backgroundColorpadding : '#ffffe00.3em'
, fontSize : ((skin && (skin == 'monobook' || skin == 'modern')) ? '127%' : '100%')
, padding: '0.3em'
, fontSize: (conf.skin === 'monobook' ? '127%' : '100%')
// Scale up to default text size
},
 
editor : null,
 
wiki_read : false,
is_rtl : false,
 
move_listening : false,
is_tracking : false,
is_adding : false,
is_editing : false,
 
zoom_threshold : 8.0,
zoom_factor : 4.0,
 
install_attempts : 0,
max_install_attempts : 10, // Maximum 5 seconds
 
imgs_with_notes : [],
thumbs : [],
other_images : [],
 
// Fallback
indication_icon : '//upload.wikimedia.org/wikipedia/commons/8/8a/Gtk-dialog-info-14px.png',
 
install : function (config)
{
if (typeof ImageAnnotator_disable !== 'undefined' && !!ImageAnnotator_disable) return;
if (!config || ImageAnnotator_config != null) return;
Line 1,451 ⟶ 1,471:
// Determine whether we have XmlHttp. We try to determine this here to be able to avoid
// doing too much work.
if ( window.XMLHttpRequest
&& typeof LAPI !== 'undefined'
&& typeof LAPI.Ajax !== 'undefined'
&& typeof LAPI.Ajax.getRequest !== 'undefined'
) { )
{
self.haveAjax = (LAPI.Ajax.getRequest() != null);
self.haveAjax = (LAPI.Ajax.getRequest() != null);
self.ajaxQueried = true;
} else {
self.haveAjax = true; // A pity. May occur on IE. We'll check again later on.
self.ajaxQueried = false;
}
 
// We'll include self.haveAjax later on once more, to catch the !ajaxQueried case.
self.may_edit = conf.wgNamespaceNumber >= 0 && conf.wgArticleId > 0 && self.haveAjax && config.editingEnabled();
 
function namespaceCheck (list) {
{
if (!list || !$.isArray(list)) return false;
if (!list || Object.prototype.toString.call(list) !== '[object Array]') return false;
for (var i = 0; i < list.length; i++) {
if (conf.wgNamespaceIds
&& typeof list[i] === 'string'
&& (list[i] === '*'
|| conf.wgNamespaceIds[list[i].toLowerCase().replace(/ /g, '_')] === conf.wgNamespaceNumber
)
)
Line 1,485 ⟶ 1,507:
// but overrideable by per-image rules. If set, it's not overrideable by per-image rules.
//
if ( !self.haveAjax
|| !config.generalImagesEnabled()
|| namespaceCheck (window.ImageAnnotator_no_images || null)
) { )
{
self.rules.inline.show = false;
self.rules.thumbs.show = false;
self.rules.shared.show = false;
} else {
if ( !self.haveAjax
|| !config.thumbsEnabled()
|| namespaceCheck(window.ImageAnnotator_no_thumbs || null)
) { )
{
self.rules.thumbs.show = false;
}
if (conf.wgNamespaceNumber === 6)
self.rules.shared.show = true;
else if ( !config.sharedImagesEnabled()
|| namespaceCheck(window.ImageAnnotator_no_shared || null)
) { )
{
self.rules.shared.show = false;
}
if (namespaceCheck(window.ImageAnnotator_icon_images || null))
self.rules.inline.icon = true;
if (namespaceCheck(window.ImageAnnotator_icon_thumbs || null))
self.rules.thumbs.icon = true;
}
 
// User rule for displaying captions on images in articles
self.hideCaptions = namespaceCheck(window.ImageAnnotator_hide_captions || null);
 
var do_images = typeof self.rules.inline.show === 'undefined' || self.rules.inline.show;
Line 1,526 ⟶ 1,551:
self.rules.shared.show = false;
}
if ( typeof self.rules.inline.show === 'undefined'
&& rules.className.indexOf('wpImageAnnotatorDisplay') >= 0
) { )
{
self.rules.inline.show = true;
}
Line 1,534 ⟶ 1,560:
self.rules.thumbs.show = false;
}
if ( typeof self.rules.thumbs.show === 'undefined'
&& rules.className.indexOf('wpImageAnnotatorThumbDisplay') >= 0
) { )
{
self.rules.thumbs.show = true;
}
Line 1,545 ⟶ 1,572:
self.rules.thumbs.icon = true;
}
if (rules.className.indexOf('wpImageAnnotatorOnlyLocal') >= 0) {
{
self.rules.shared.show = false;
}
Line 1,600 ⟶ 1,628:
// Guard against other scripts adding aribtrary numbers of divs (dshuf for instance!)
var is_other = true;
while (up && up.nodeName.toLowerCase() === 'div' && is_other) {
up = up.parentNode;
if (up) is_other = (' ' + up.className + ' ').indexOf(' gallerybox ') < 0;
Line 1,609 ⟶ 1,637:
}
} else {
self.imgs_with_notes = document.getElementsByClassName (document, '*', 'wpImageAnnotatorEnable');
if (do_thumbs)
self.thumbs = getElementsByClassName (document, 'div', 'thumbinner'); // No galleries!
self.thumbs = document.getElementsByClassName('thumbinner');
}
if ( conf. wgNamespaceNumber === 6
|| (self.imgs_with_notes.length)
|| (self.thumbs.length)
|| (self.other_images.length)
) { )
{
// Publish parts of config.
ImageAnnotator.UI = config.UI;
self.outer_border = config.outer_border;
self.inner_border = config.inner_border;
self.active_border = config.active_border;
self.new_border = config.new_border;
self.wait_for_required_libraries();
}
},
 
wait_for_required_libraries : function ()
{
if (typeof Tooltip === 'undefined' || typeof LAPI === 'undefined') {
if (IA.install_attempts++ < IA.max_install_attempts) {
setTimeout(IA.wait_for_required_libraries, 500); // 0.5 sec.
Line 1,642 ⟶ 1,671:
},
 
setup: function ()
{
var self = IA;
self.imgs = [];
Line 1,649 ⟶ 1,679:
self.is_rtl =
LAPI.DOM.hasClass(document.body, 'rtl')
|| ( LAPI.DOM.currentStyle // Paranoia: added recently, not everyone might have it
&& LAPI.DOM.currentStyle(document.body, 'direction') === 'rtl'
)
;
 
var stylepath = mw.config.get('stylepath') || '/skin';
 
// Use this to temporarily display an image off-screen to get its dimensions
var testImgDiv =
LAPI.make('div', null, {
{ display: 'none', position: 'absolute', width: '300px',
, overflow: 'hidden', overflowX: 'hidden', left: '-10000px'
});
);
document.body.insertBefore(testImgDiv, document.body.firstChild);
 
function img_check (img, is_other) {
{
var srcW = parseInt (img.getAttribute('width', 2), 10);
var srcH = parseInt (img.getAttribute('height', 2), 10);
Line 1,674 ⟶ 1,708:
// If the image is currently hidden, its clientWidth and clientHeight are not set. Try
// harder to get the true width and height:
if ((!w || !h) && img.style.display !== 'none') {
var copied = img.cloneNode(true);
copied.style.display = '';
Line 1,685 ⟶ 1,719:
}
// Quit if the image wasn't loaded properly for some reason:
if (w !== srcW || h !== srcH) return null;
// Exclude system images
if (img.src.contains(conf.stylepath)) return null;
// Only if within a link
if (img.parentNode.nodeName.toLowerCase() !== 'a') return null;
if (is_other) {
// Only if the img-within-link construction is within some element that may contain a div!
Line 1,702 ⟶ 1,736:
// Exclude any that are within an image note!
var up = img.parentNode.parentNode;
while (up !== document.body) {
if (LAPI.DOM.hasClass(up, IA.annotation_class)) return null;
up = up.parentNode;
Line 1,709 ⟶ 1,743:
}
 
function setup_one (scope) {
var file_div = scope;
var is_thumb =
scope !== document
&& scope.nodeName.toLowerCase() === 'div'
&& LAPI.DOM.hasClass(scope, 'thumbinner')
;
var is_other = scope.nodeName.toLowerCase() === 'img';
if (is_other && self.imgs.length && scope === self.imgs[0]) return null;
if (scope === document) {
file_div = LAPI.$('file');
} else if (!is_thumb && !is_other) {
file_div = scope.getElementsByClassName(scope, 'div', 'wpImageAnnotatorFile');
if (!file_div || file_div.length !== 1) return null;
file_div = file_div[0];
}
if (!file_div) return null;
var img = null;
if (scope === document) {
img = LAPI.WP.getPreviewImage(conf.wgTitle);
// TIFFs may be multi-paged: allow only for single-page TIFFs
if ( document.pageselector ) img = null;
Line 1,739 ⟶ 1,773:
}
if (!img) return null;
var dim = img_check (img, is_other);
if (!dim) return null;
// Conditionally exclude shared images.
if ( scope !== document
&& !self.rules.shared.show
&& ImageAnnotator_config.imageIsFromSharedRepository(img.src)
Line 1,748 ⟶ 1,782:
return null;
var name = null;
if (scope === document) {
name = conf.wgPageName;
} else {
name = LAPI.WP.pageFromLink(img.parentNode);
Line 1,782 ⟶ 1,816:
}
}
return { scope : scope
,file_div : file_div
,img : img
,realName : name
,isThumbnail: is_thumb
,isOther : is_other
,thumb : {width: dim.width, height: dim.height}
,iconOnly : icon_only
,noCaption : no_caption
};
}
 
function setup_images (list) {
{
Array.forEach(list,
function (elem) {
var desc = setup_one (elem);
if (desc) self.imgs[self.imgs.length] = desc;
}
Line 1,803 ⟶ 1,838:
}
 
if (conf.wgNamespaceNumber === 6) {
setup_images ([document]);
self.may_edit = self.may_edit && (self.imgs.length === 1);
setup_images (self.imgs_with_notes);
} else {
setup_images (self.imgs_with_notes);
self.may_edit = self.may_edit && (self.imgs.length === 1);
}
 
Line 1,815 ⟶ 1,850:
 
if (self.haveAjax) {
setup_images (self.thumbs);
setup_images (self.other_images);
}
 
Line 1,827 ⟶ 1,862:
// need them. This has in particular a benefit if we do have to query for the file sizes below.
 
if (self.imgs.length === 1 && self.imgs[0].scope === document && !self.haveAjax) {
// Try to get the full size without Ajax.
self.imgs[0].full_img = LAPI.WP.fullImageSizeFromPage();
Line 1,852 ⟶ 1,887:
 
var to_do = names.length;
var done = 0;
 
function check_done (length) {
{
done += length;
if (done >= names.length) {
Line 1,862 ⟶ 1,898:
}
 
function make_calls (execute_call, url_limit) {
{
function build_titles(from, length, url_limit) {
function build_titles (from, length, url_limit)
{
var done = 0;
var text = '';
Line 1,878 ⟶ 1,916:
}
 
var start = 0, chunk = 0, params;
var start = 0;
while (to_do > 0) {
params = build_titles (start, Math.min(50, to_do), url_limit);
execute_call (params.n, params.text);
to_do -= params.n;
start += params.n;
Line 1,888 ⟶ 1,925:
}
 
function set_info (json) {
{
function get_size(info) {
if (!info.imageinfo || info.imageinfo.length === 0) return;
var title = info.title.replace(/ /g, '_');
var indices = cache[title];
if (!indices) return;
Array.forEach(
indices
, function (i) {
self.imgs[i].full_img = { width : info.imageinfo[0].width
,height: info.imageinfo[0].height};
self.imgs[i].has_page = (typeof info.missing === 'undefined');
self.imgs[i].isLocal = !info.imagerepository || info.imagerepository === 'local';
if (i !== 0 || !self.may_edit || !info.protection || conf.wgNamespaceNumber !== 6) return;
// Care about the protection settings
var protection = Array.any(info.protection, function (e) {
return (e.type === 'edit' ? e : null);
});
self.may_edit =
!protection
|| (wgUserGroups && wgUserGroups.join(' ').contains(protection.level))
;
}
);
}
try {
if (json && json.query && json.query.pages) {
function get_size (info) {
if (!info.imageinfo || info.imageinfo.length === 0) return;
var title = info.title.replace(/ /g, '_');
var indices = cache[title];
if (!indices) return;
Array.forEach(
indices
, function (i) {
self.imgs[i].full_img = { width : info.imageinfo[0].width
,height: info.imageinfo[0].height};
self.imgs[i].has_page = (typeof info.missing === 'undefined');
self.imgs[i].isLocal = !info.imagerepository || info.imagerepository == 'local';
if (i != 0 || !self.may_edit || !info.protection || wgNamespaceNumber != 6) return;
// Care about the protection settings
var protection = Array.any(info.protection, function (e) {
return (e.type == 'edit' ? e : null);
});
self.may_edit =
!protection
|| (wgUserGroups && wgUserGroups.join(' ').contains(protection.level))
;
}
);
}
for (var page in json.query.pages) {
get_size (json.query.pages[page]);
}
} // end if
Line 1,927 ⟶ 1,965:
// prompt by using getScript instead of parseWikitext in this case.
ImageAnnotator.info_callbacks = [];
var template = mw.util.wikiScript('api')wgServer + wgScriptPath + '/api.php?format=json&action=query&format=json'
+ '&prop=info|imageinfo&inprop=protection&iiprop=size'
+ '&titles=&callback=ImageAnnotator.info_callbacks[].callback';
if (template.startsWith('//')) template = document.___location.protocol + template; // Avoid protocol-relative URIs (IE7 bug)
make_calls (
function (length, titles) {
var idx = ImageAnnotator.info_callbacks.length;
ImageAnnotator.info_callbacks[idx] = {
{ callback : function (json) {
set_info (json);
ImageAnnotator.info_callbacks[idx].done = true;
if (ImageAnnotator.info_callbacks[idx].script) {
LAPI.DOM.removeNode(ImageAnnotator.info_callbacks[idx].script);
ImageAnnotator.info_callbacks[idx].script = null;
}
check_done (length);
},
,done : false
};
ImageAnnotator.info_callbacks[idx].script =
IA.getScript(
Line 1,956 ⟶ 1,994:
// script (and call the callback) synchronously before the assignment is done. Clean
// up in that case.
if ( ImageAnnotator.info_callbacks && ImageAnnotator.info_callbacks[idx]
&& ImageAnnotator.info_callbacks[idx].done && ImageAnnotator.info_callbacks[idx].script)
) {
LAPI.DOM.removeNode(ImageAnnotator.info_callbacks[idx].script);
ImageAnnotator.info_callbacks[idx].script = null;
}
},
, (LAPI.Browser.is_ie ? 1950 : 4000) - template.length // Some slack for caching parameters
(LAPI.Browser.is_ie ? 1950 : 4000) - template.length
);
} else {
make_calls (
function (length, titles) {
LAPI.Ajax.apiGet(
'query',
, { titles : titles
titles: titles,prop : 'info|imageinfo'
prop ,inprop : 'info|imageinfoprotection',
inprop ,iiprop : 'protectionsize',
iiprop: 'size'}
}, function (request, json_result) {
function (request, set_info (json_result) {;
set_info check_done (json_resultlength);
check_done(length);}
}, function () {check_done (length);}
function () {
check_done(length);
}
);
}
Line 1,990 ⟶ 2,024:
},
 
setup_ui : function ()
{
// Complete the UI object we've gotten from config.
 
ImageAnnotator.UI.ready = false;
ImageAnnotator.UI.repo = null;
ImageAnnotator.UI.needs_plea = false;
 
Line 2,018 ⟶ 2,053:
};
 
ImageAnnotator.UI.setup = function () {
{
if (ImageAnnotator.UI.repo) return;
var self = ImageAnnotator.UI;
var node = LAPI.make('div', null, { display: 'none' });
var item;
document.body.appendChild(node);
if (typeof UIElements === 'undefined') {
self.basic = true;
self.repo = {};
for (var item in self.defaults) {
node.innerHTML = self.defaults[item];
self.repo[item] = node.firstChild;
Line 2,035 ⟶ 2,070:
self.basic = false;
self.repo = UIElements.emptyRepository(self.defaultLanguage);
for (var item in self.defaults) {
node.innerHTML = self.defaults[item];
UIElements.setEntry(item, self.repo, node.firstChild);
Line 2,045 ⟶ 2,080:
};
 
ImageAnnotator.UI.get = function (id, basic, no_plea) {
{
var self = ImageAnnotator.UI;
if (!self.repo) self.setup();
var result = null;
var add_plea = false;
if (self.basic) {
result = self.repo[id];
} else {
result = UIElements.getEntry(id, self.repo, conf.wgUserLanguage, null);
add_plea = !result;
if (!result) result = UIElements.getEntry(id, self.repo);
Line 2,063 ⟶ 2,099:
if (wgServer.contains('/commons') && add_plea && !no_plea) {
// Add a translation plea.
if (result.nodeName.toLowerCase() === 'div') {
result.appendChild(self.get_plea());
} else {
Line 2,075 ⟶ 2,111:
};
 
ImageAnnotator.UI.get_plea = function () {
{
var self = ImageAnnotator.UI;
var translate = self.get('wpTranslate', false, true) || 'translate';
Line 2,082 ⟶ 2,119:
span.appendChild(
LAPI.DOM.makeLink(
mw.util.getUrl(wgServer + wgScript + '?title=MediaWiki_talk:ImageAnnotatorTexts', {
+ '&action=edit&section=new&withJS=MediaWiki: 'editImageAnnotatorTranslator.js',
+ '&language=' + section: 'new',wgUserLanguage
, translate
withJS: 'MediaWiki:ImageAnnotatorTranslator.js',
, (typeof translate === 'string' ? translate : LAPI.DOM.getInnerText(translate).trim())
language: conf.wgUserLanguage
}),
translate,
(typeof translate === 'string' ? translate : LAPI.DOM.getInnerText(translate).trim())
)
);
Line 2,096 ⟶ 2,130:
};
 
ImageAnnotator.UI.init = function (html_text_or_json) {
{
var text;
if (typeof html_text_or_json === 'string')
text = html_text_or_json;
else if ( typeof html_text_or_json !== 'undefined'
&& typeof html_text_or_json.parse !== 'undefined'
&& typeof html_text_or_json.parse.text !== 'undefined'
&& typeof html_text_or_json.parse.text['*'] !== 'undefined'
)
text = html_text_or_json.parse.text['*'];
else
Line 2,114 ⟶ 2,149:
}
 
var node = LAPI.make('div', null, { display: 'none' });
document.body.appendChild(node);
try {
Line 2,128 ⟶ 2,163:
 
var ui_page = '{{MediaWiki:ImageAnnotatorTexts'
+ (conf.wgUserLanguage !== wgContentLanguage ? '|lang=' + conf.wgUserLanguage : '')
+ '|live=1}}';
 
function get_ui_no_ajax () {
{
var url =
mw.util.wikiScript('api')wgServer + wgScriptPath + '/api.php?format=json&action=parse&pst&text='
+ encodeURIComponent(ui_page) + '&title=API&prop=text&format=json'
+ '&callback=ImageAnnotator.UI.init&maxage=14400&smaxage=14400'
;
Line 2,143 ⟶ 2,179:
}
 
function get_ui () {
{
IA.haveAjax = (LAPI.Ajax.getRequest() != null);
IA.haveAjax = (LAPI.Ajax.getRequest() != null);
IA.ajaxQueried = true;
 
Line 2,173 ⟶ 2,210:
get_ui_no_ajax ();
} else {
get_ui ();
}
},
 
setup_step_two : function ()
{
var self = IA;
 
Line 2,199 ⟶ 2,237:
},
 
complete_setup : function ()
{
// We can be sure to have the UI here because this is called only when the ready event of the
function track(evt) {
// UI object is fired.
var self = IA;
 
// Check edit permissions
if (self.may_edit && typeof wgRestrictionEdit !== 'undefined' ) {
self.may_edit =
( (wgRestrictionEdit.length === 0 || wgUserGroups && wgUserGroups.join(' ').contains('sysop'))
|| ( wgRestrictionEdit.length === 1 && wgRestrictionEdit[0] === 'autoconfirmed'
&& wgUserGroups && wgUserGroups.join(' ').contains('confirmed') // confirmed & autoconfirmed
)
);
}
 
if (self.may_edit) {
// Check whether the image is local. Don't allow editing if the file is remote.
var sharedUpload = getElementsByClassName (document, 'div', 'sharedUploadNotice');
self.may_edit = (!sharedUpload || sharedUpload.length === 0);
}
if (self.may_edit && wgNamespaceNumber != 6) {
// Only allow edits if the stored page name matches the current one.
var img_page_name =
getElementsByClassName (self.imgs[0].scope, '*', 'wpImageAnnotatorPageName');
if (img_page_name && img_page_name.length)
img_page_name = LAPI.DOM.getInnerText(img_page_name[0]);
else
img_page_name = '';
self.may_edit = (img_page_name.replace(/ /g, '_') == wgTitle.replace(/ /g, '_'));
}
 
if (self.may_edit && self.ajaxQueried) self.may_edit = self.haveAjax;
 
// Now create viewers for all images
self.viewers = new Array (self.imgs.length);
for (var i = 0; i < self.imgs.length; i++) {
self.viewers[i] = new ImageNotesViewer (self.imgs[i], i === 0 && self.may_edit);
}
 
if (self.may_edit) {
 
// Respect user override for zoom, if any
self.zoom_threshold = ImageAnnotator_config.zoom_threshold;
if ( typeof window.ImageAnnotator_zoom_threshold !== 'undefined'
&& !isNaN (window.ImageAnnotator_zoom_threshold)
&& window.ImageAnnotator_zoom_threshold >= 0.0
)
{
// If somebody sets it to a nonsensical high value, that's his or her problem: there won't be any
// zooming.
self.zoom_threshold = window.ImageAnnotator_zoom_threshold;
}
// Adapt zoom threshold for small thumbnails or images with a very lopsided width/height ratio,
// but only if we *can* zoom at least twice
if ( self.viewers[0].full_img.width > 300
&& Math.min(self.viewers[0].factors.dx, self.viewers[0].factors.dy) >= 2.0
)
{
if ( self.viewers[0].thumb.width < 400
|| self.viewers[0].thumb.width / self.viewers[0].thumb.height > 2.0
|| self.viewers[0].thumb.height / self.viewers[0].thumb.width > 2.0
)
{
self.zoom_threshold = 0; // Force zooming
}
}
 
self.editor = new ImageAnnotationEditor ();
 
function track (evt) {
evt = evt || window.event;
if (self.is_adding) self.update_zoom(evt);
Line 2,206 ⟶ 2,313:
var mouse_pos = LAPI.Pos.mousePosition(evt);
if (!LAPI.Pos.isWithin(self.cover, mouse_pos.x, mouse_pos.y)) return;
var origin = LAPI.Pos.position(self.cover);
// Make mouse pos relative to cover
mouse_pos.x = mouse_pos.x - origin.x;
Line 2,212 ⟶ 2,319:
if (mouse_pos.x >= self.base_x) {
self.definer.style.width = '' + (mouse_pos.x - self.base_x) + 'px';
self.definer.style.left = '' + self.base_x + 'px';
} else {
self.definer.style.width = '' + (self.base_x - mouse_pos.x) + 'px';
self.definer.style.left = '' + mouse_pos.x + 'px';
}
if (mouse_pos.y >= self.base_y) {
self.definer.style.height = '' + (mouse_pos.y - self.base_y) + 'px';
self.definer.style.top = '' + self.base_y + 'px';
} else {
self.definer.style.height = '' + (self.base_y - mouse_pos.y) + 'px';
self.definer.style.top = '' + mouse_pos.y + 'px';
}
return LAPI.Evt.kill(evt);
}
 
function pause (evt) {
LAPI.Evt.remove(document, 'mousemove', track, true);
if (!LAPI.Browser.is_ie && typeof document.captureEvents === 'function')
Line 2,234 ⟶ 2,341:
}
 
function resume (evt) {
// captureEvents is actually deprecated, but I haven't succeeded to make this work with
// addEventListener only.
Line 2,245 ⟶ 2,352:
}
 
function stop_tracking (evt) {
evt = evt || window.event;
// Check that we're within the image. Note: this check can fail only on IE >= 7, on other
Line 2,282 ⟶ 2,389:
} else {
// We have a div with some extent: remove event capturing and create a new annotation
var new_note = new ImageAnnotation (self.definer, self.viewers[0], -1);
self.viewers[0].register(new_note);
self.editor.editNote(new_note);
Line 2,292 ⟶ 2,399:
}
 
function start_tracking (evt) {
{
if (!self.is_tracking) {
self.is_tracking = true;
Line 2,298 ⟶ 2,406:
// Set the position, size 1
var mouse_pos = LAPI.Pos.mousePosition(evt);
var origin = LAPI.Pos.position(self.cover);
self.base_x = mouse_pos.x - origin.x;
self.base_y = mouse_pos.y - origin.y;
Object.merge(
{ left : '' + self.base_x + 'px'
,top : '' + self.base_y + 'px'
,width : '0px'
,height : '0px'
,display: ''
}
Line 2,325 ⟶ 2,433:
}
 
function add_new (evt) {
if (!self.canEdit()) return;
 
Line 2,333 ⟶ 2,441:
cover.style.cursor = 'crosshair';
self.definer =
LAPI.make(
'div', null, {
,{ border : '1px solid ' + IA.new_border,
,display : 'none',
,position : 'absolute',
,top : '0px',
,left : '0px',
,width : '0px',
,height : '0px',
,padding : '0',
,lineHeight : '0px' // IE needs this, even though there are no lines within
lineHeight ,fontSize : '0px', // IE
,zIndex : cover.style.zIndex - 2 // Below the mouse capture div
// IE
fontSize: '0px', }
);
// Below the mouse capture div
zIndex: cover.style.zIndex - 2
});
self.viewers[0].img_div.appendChild(self.definer);
// Enter mouse-tracking mode to define extent of view. Mouse cursor is outside of image,
Line 2,355 ⟶ 2,462:
self.show_cover();
self.is_tracking = false;
self.is_adding = true;
LAPI.Evt.attach(cover, 'mousedown', start_tracking);
resume ();
Line 2,374 ⟶ 2,481:
}
 
self.button_div = LAPI.make('div');
// We can be sure to have the UI here because this is called only when the ready event of the
self.viewers[0].main_div.appendChild(self.button_div);
// UI object is fired.
var self.add_button = IA;
LAPI.DOM.makeButton(
 
'ImageAnnotationAddButton'
// Check edit permissions
, ImageAnnotator.UI.get('wpImageAnnotatorAddButtonText', true)
if (self.may_edit && typeof wgRestrictionEdit !== 'undefined' ) {
self.may_edit = , add_new
);
( (wgRestrictionEdit.length === 0 || wgUserGroups && wgUserGroups.join(' ').contains('sysop'))
var add_plea = ImageAnnotator.UI.needs_plea;
|| ( wgRestrictionEdit.length === 1 && wgRestrictionEdit[0] === 'autoconfirmed'
self.button_div.appendChild(self.add_button);
&& wgUserGroups && wgUserGroups.join(' ').contains('confirmed') // confirmed & autoconfirmed
self.help_link = self.createHelpLink();
)
if (self.help_link) );{
self.button_div.appendChild(document.createTextNode('\xa0'));
self.button_div.appendChild(self.help_link);
}
if (add_plea && wgServer.contains('/commons'))
self.button_div.appendChild(ImageAnnotator.UI.get_plea());
 
if (self.may_edit) {
// Check whether the image is local. Don't allow editing if the file is remote.
var sharedUpload = document.getElementsByClassName('sharedUploadNotice');
self.may_edit = (!sharedUpload || sharedUpload.length === 0);
}
if (self.may_edit && conf.wgNamespaceNumber !== 6) {
// Only allow edits if the stored page name matches the current one.
var img_page_name = self.imgs[0].scope.getElementsByClassName('wpImageAnnotatorPageName');
if (img_page_name && img_page_name.length)
img_page_name = LAPI.DOM.getInnerText(img_page_name[0]);
else
img_page_name = '';
self.may_edit = (img_page_name.replace(/ /g, '_') === conf.wgTitle.replace(/ /g, '_'));
}
 
if (self.may_edit && self.ajaxQueried) self.may_edit = self.haveAjax;
 
// Now create viewers for all images
self.viewers = new Array (self.imgs.length);
for (var i = 0; i < self.imgs.length; i++) {
self.viewers[i] = new ImageNotesViewer (self.imgs[i], i === 0 && self.may_edit);
}
 
if (self.may_edit) {
// Respect user override for zoom, if any
self.zoom_threshold = ImageAnnotator_config.zoom_threshold;
if ( typeof window.ImageAnnotator_zoom_threshold !== 'undefined'
&& !isNaN (window.ImageAnnotator_zoom_threshold)
&& window.ImageAnnotator_zoom_threshold >= 0.0
) {
// If somebody sets it to a nonsensical high value, that's his or her problem: there won't be any
// zooming.
self.zoom_threshold = window.ImageAnnotator_zoom_threshold;
}
// Adapt zoom threshold for small thumbnails or images with a very lopsided width/height ratio,
// but only if we *can* zoom at least twice
if ( self.viewers[0].full_img.width > 300
&& Math.min(self.viewers[0].factors.dx, self.viewers[0].factors.dy) >= 2.0
) {
if ( self.viewers[0].thumb.width < 400
|| self.viewers[0].thumb.width / self.viewers[0].thumb.height > 2.0
|| self.viewers[0].thumb.height / self.viewers[0].thumb.width > 2.0
) {
self.zoom_threshold = 0; // Force zooming
}
}
 
self.editor = new ImageAnnotationEditor();
 
self.button_div = LAPI.make('div');
self.viewers[0].main_div.appendChild(self.button_div);
self.add_button =
LAPI.DOM.makeButton(
'ImageAnnotationAddButton'
, ImageAnnotator.UI.get('wpImageAnnotatorAddButtonText', true)
, add_new
);
var add_plea = ImageAnnotator.UI.needs_plea;
self.button_div.appendChild(self.add_button);
self.help_link = self.createHelpLink();
if (self.help_link) {
self.button_div.appendChild(document.createTextNode('\xa0'));
self.button_div.appendChild(self.help_link);
}
if (add_plea && wgServer.contains('/commons'))
self.button_div.appendChild(ImageAnnotator.UI.get_plea());
} // end if may_edit
 
// Get the file description pages of thumbnails. Figure out for which viewers we need to do this.
var cache = {};
var get_local = [];
var get_foreign = [];
Array.forEach(self.viewers, function (viewer, idx) {
Line 2,481 ⟶ 2,526:
// API limits and to keep the URL length below the limit for the foreign_repo calls.
 
function make_calls (list, execute_call, url_limit) {
{
function composer (list, from, length, url_limit) {
function composecomposer (list, from, length, url_limit) {
{
function compose (list, from, length, url_limit)
{
var text = '';
var done = 0;
Line 2,503 ⟶ 2,551:
// in the result, which would make us load the full images, which is desastrous if there are
// many thumbs to large images on the page.
if (done === 5) break;
}
return {text: text, n: done};
Line 2,509 ⟶ 2,557:
 
var param = compose (list, from, length, url_limit);
execute_call (param.text);
return param.n;
}
Line 2,521 ⟶ 2,569:
}
 
var divRE = /(<\s*div\b)|(<\/\s*div\s*>)/ig;
var blockStart = '<div class="wpImageAnnotatorInlineImageWrapper"';
var inlineNameEnd = '</span>';
var noteStart = '<div id="image_annotation_note_';
var noteControlRE = /<div\s*class="(wpImageAnnotatorInlinedRules|image_annotation_colors")(\S|\s)*?\/div>/g;
 
Line 2,531 ⟶ 2,579:
// additional images we'd rather not load when we add this (X)HTML to the DOM. Therefore, we
// strip out everything but the notes.
function strip_noise (html) {
var result = '';
var m;
Line 2,539 ⟶ 2,587:
// Now collect pages
while (idx < l && i >= idx) {
var j = html.indexOf(inlineNameEnd, i + blockStart.length);
if (j < i + blockStart.length) break;
result += html.substring(i, j + inlineNameEnd.length);
idx = j + inlineNameEnd.length;
// Now collect all image image notes for that page
var note_begin = 0, next_block = html.indexOf(blockStart, idx);
Line 2,586 ⟶ 2,634:
}
 
function setup_thumb_viewers (html_text) {
{
var node = LAPI.make('div', null, {display: 'none'});
document.body.appendChild(node);
try {
node.innerHTML = strip_noise (html_text);
var pages = node.getElementsByClassName (node, 'div', 'wpImageAnnotatorInlineImageWrapper');
for (var i = 0; pages && i < pages.length; i++) {
var notes = getElementsByClassName (pages[i].getElementsByClassName(, 'div', self.annotation_class);
if (!notes || notes.length === 0) continue;
var page = self.getItem('inline_name', pages[i]);
if (!page) continue;
page = page.replace(/ /g, '_');
var viewers = cache[page] || cache['File:' + page.substring(page.indexOf(':') + 1)];
if (!viewers || viewers.length === 0) continue;
// Update rules.
var rules = getElementsByClassName (pages[i].getElementsByClassName(, 'div', 'wpImageAnnotatorInlinedRules');
var local_rules = {
{ inline: Object.clone(IA.rules.inline),
,thumbs: Object.clone(IA.rules.thumbs)
};
if (rules && rules.length) {
rules = rules[0];
if ( typeof local_rules.inline.show === 'undefined'
&& LAPI.DOM.hasClass(rules, 'wpImageAnnotatorNoInlineDisplay')
) { )
{
local_rules.inline.show = false;
}
if ( typeof local_rules.inline.icon === 'undefined'
&& LAPI.DOM.hasClass(rules, 'wpImageAnnotatorInlineDisplayIcon')
) { )
{
local_rules.inline.icon = true;
}
if ( typeof local_rules.thumbs.show === 'undefined'
&& LAPI.DOM.hasClass(rules, 'wpImageAnnotatorNoThumbs')
) { )
{
local_rules.thumbs.show = false;
}
if ( typeof local_rules.thumbs.icon === 'undefined'
&& LAPI.DOM.hasClass(rules, 'wpImageAnnotatorThumbDisplayIcon')
) { )
{
local_rules.thumbs.icon = true;
}
Line 2,647 ⟶ 2,700:
if (!self.viewers[v].isThumbnail || local_rules.thumbs.show) {
self.viewers[v].scope = pages[i];
self.viewers[v].setup( self.viewers[v].isThumbnail && local_rules.thumbs.icon
|| self.viewers[v].isOther && local_rules.inline.icon);
}
Line 2,658 ⟶ 2,711:
ImageAnnotator.script_callbacks = [];
 
function make_script_calls (list, api) {
{
var template = api + '?action=parse&pst&text=&prop=text&format=json'
+ '&maxage=1800&smaxage=1800&uselang=' + conf.wgUserLanguage //see bugzilla 22764
+ '&callback=ImageAnnotator.script_callbacks[].callback';
if (template.startsWith('//')) template = document.___location.protocol + template; // Avoid protocol-relative URIs (IE7 bug)
make_calls (
list
, function (text) {
var idx = ImageAnnotator.script_callbacks.length;
ImageAnnotator.script_callbacks[idx] = {
{ callback : function (json) {
if (json && json.parse && json.parse.text && json.parse.text['*']) {
setup_thumb_viewers (json.parse.text['*']);
}
ImageAnnotator.script_callbacks[idx].done = true;
if (ImageAnnotator.script_callbacks[idx].script) {
LAPI.DOM.removeNode(ImageAnnotator.script_callbacks[idx].script);
ImageAnnotator.script_callbacks[idx].script = null;
}
},
,done : false
};
ImageAnnotator.script_callbacks[idx].script =
IA.getScript(
Line 2,686 ⟶ 2,740:
, true // No local caching!
);
if ( ImageAnnotator.script_callbacks && ImageAnnotator.script_callbacks[idx]
&& ImageAnnotator.script_callbacks[idx].done && ImageAnnotator.script_callbacks[idx].script)
) {
LAPI.DOM.removeNode(ImageAnnotator.script_callbacks[idx].script);
ImageAnnotator.script_callbacks[idx].script = null;
Line 2,698 ⟶ 2,752:
 
if ((!window.XMLHttpRequest && !!window.ActiveXObject) || !self.haveAjax) {
make_script_calls(get_local, mw.util.wikiScript(wgServer + wgScriptPath + '/api.php'));
} else {
make_calls(
Line 2,720 ⟶ 2,774:
// character (2048 in the path part), and servers also may impose limits (on the WMF servers,
// the limit appears to be 8kB).
make_script_calls (get_foreign, ImageAnnotator_config.sharedRepositoryAPI());
},
 
show_zoom : function ()
{
var self = IA;
if ( ( self.viewers[0].factors.dx < self.zoom_threshold
&& self.viewers[0].factors.dy < self.zoom_threshold
)
|| Math.max(self.viewers[0].factors.dx, self.viewers[0].factors.dy) < 2.0
) { )
{
// Below zoom threshold, or full image not even twice the size of the preview
return;
Line 2,735 ⟶ 2,791:
if (!self.zoom) {
self.zoom =
LAPI.make('div',
{ 'div'
, {id : 'image_annotator_zoom' },
, { overflow : 'hidden'
overflow ,width : 'hidden200px',
width ,height : '200px',
height ,position : '200pxabsolute',
position ,display : 'absolutenone',
display ,top : 'none0px',
top ,left : '0px',
left ,border : '0px2px solid #666666',
border: '2px,backgroundColor solid: #666',white'
,zIndex : 2050 // On top of everything
backgroundColor: 'white',
zIndex: 2050 // On top of everything}
}
);
var src = self.viewers[0].img.getAttribute('src', 2);
Line 2,754 ⟶ 2,810:
if (self.zoom_factor > self.viewers[0].factors.dx || self.zoom_factor > self.viewers[0].factors.dy)
self.zoom_factor = Math.min(self.viewers[0].factors.dx, self.viewers[0].factors.dy);
self.zoom.appendChild(LAPI.make('div', null, { position : 'relative' }));
// Calculate zoom size and source link
var zoom_width = Math.floor(self.viewers[0].thumb.width * self.zoom_factor);
var zoom_height = Math.floor(self.viewers[0].thumb.height * self.zoom_factor);
var zoom_src = null;
// For SVGs, always use a scaled PNG for the zoom.
if (zoom_width > 0.9 * self.viewers[0].full_img.width && src.search(/\.svg\.png$/i) < 0) {
Line 2,763 ⟶ 2,820:
// well get the full image instead of a scaled version.
self.zoom_factor = Math.min(self.viewers[0].factors.dx, self.viewers[0].factors.dy);
zoom_width = self.viewers[0].full_img.width;
zoom_height = self.viewers[0].full_img.height;
}
// Construct the initial zoomed image. We need to clone; if we create a completely
Line 2,777 ⟶ 2,834:
LAPI.make(
'div', null
, { width : '1px'
,height : '200px'
,borderLeft : '1px solid red'
,position : 'absolute'
,top : '0px'
,left : '100px'
}
)
Line 2,789 ⟶ 2,846:
LAPI.make(
'div', null
, { width : '200px'
,height : '1px'
,borderTop : '1px solid red'
,position : 'absolute'
,top : '100px'
,left : '0px'
}
)
Line 2,818 ⟶ 2,875:
},
 
update_zoom : function (evt)
{
if (!evt) return; // We need an event to calculate positions!
var self = IA;
if (!self.zoom) return;
var mouse_pos = LAPI.Pos.mousePosition(evt);
var origin = LAPI.Pos.position(self.cover);
if (!LAPI.Pos.isWithin(self.cover, mouse_pos.x, mouse_pos.y)) {
IA.hide_zoom();
Line 2,831 ⟶ 2,889:
var dy = mouse_pos.y - origin.y;
// dx, dy is the offset within the preview image. Align the zoom image accordingly.
var top = - dy * self.zoom_factor + 100;
var left = - dx * self.zoom_factor + 100;
self.zoom.firstChild.firstChild.style.top = '' + top + 'px';
self.zoom.firstChild.firstChild.style.left = '' + left + 'px';
self.zoom.style.top = mouse_pos.y + 10 + 'px'; // Right below the mouse pointer
// Horizontally keep it in view.
var x = (self.is_rtl ? mouse_pos.x - 10 : mouse_pos.x + 10);
Line 2,846 ⟶ 2,904:
if (x < 0) x = 0;
} else {
var off = LAPI.Pos.scrollOffset();
var view = LAPI.Pos.viewport();
if (x + self.zoom.offsetWidth > off.x + view.x) x = off.x + view.x - self.zoom.offsetWidth;
Line 2,854 ⟶ 2,912:
},
 
hide_zoom : function (evt)
{
if (!IA.zoom) return;
if (evt) {
Line 2,863 ⟶ 2,922:
},
 
createHelpLink : function ()
{
var msg = ImageAnnotator.UI.get('wpImageAnnotatorHelp', false, true);
if (!msg || !msg.lastChild) return null;
Line 2,876 ⟶ 2,936:
}
}
if ( msg.childNodes.length === 1
&& msg.firstChild.nodeName.toLowerCase() === 'a'
&& !LAPI.DOM.hasClass(msg.firstChild, 'image')
) {
Line 2,886 ⟶ 2,946:
// link.
tgt = msg.lastChild;
if (tgt.nodeName.toLowerCase() !== 'a')
tgt = mwwgServer + wgArticlePath.util.getUrlreplace('$1', 'Help:Gadget-ImageAnnotator');
else
tgt = tgt.href;
 
function make_handler (tgt) {
var target = tgt;
return function (evt) {
Line 2,920 ⟶ 2,980:
},
 
get_cover : function ()
{
var self = IA;
var shim;
if (!self.cover) {
var pos = { position : 'absolute'
,left : '0px'
,top : '0px'
,width : self.viewers[0].thumb.width + 'px'
,height : self.viewers[0].thumb.height + 'px'
};
self.cover = LAPI.make('div', null, pos);
Line 2,934 ⟶ 2,995:
Object.merge(
{border: '3px solid green', top: '-3px', left: '-3px'}, self.border.style);
self.cover.style.zIndex = 2000; // Above the tooltips
if (LAPI.Browser.is_ie) {
shim = LAPI.make('iframe', {frameBorder: 0, tabIndex: -1}, pos);
shim.style.filter = 'alpha(Opacity=0)'; // Ensure transparency
// Unfortunately, IE6/SP2 has a "security setting" called "Binary and script
// behaviors". If that is disabled, filters don't work, and our iframe would
Line 2,945 ⟶ 3,006:
var imgZ = self.viewers[0].img.style.zIndex;
if (isNaN (imgZ)) imgZ = 10; // Arbitrary, positive, > 1, < 500
shim.style.zIndex = imgZ + 1;
self.ieFix = shim;
// And now the bgImage div...
shim = LAPI.make('div', null, pos);
Object.merge(
{ top : '0px'
,backgroundImage: 'url(' + self.viewers[0].img.src + ')'
,zIndex : imgZ + 2
}
, shim.style
Line 2,965 ⟶ 3,026:
shim.style.zIndex = self.cover.style.zIndex - 1;
LAPI.Evt.attach(shim, 'mousemove',
function (evt) { return LAPI.Evt.kill(evt || window.event); });
LAPI.Evt.attach(shim, 'mousedown',
function (evt) { return LAPI.Evt.kill(evt || window.event); });
LAPI.Evt.attach(shim, 'mouseup',
function (evt) { return LAPI.Evt.kill(evt || window.event); });
shim.style.cursor = 'default';
self.eventFix = shim;
Line 2,978 ⟶ 3,039:
},
 
show_cover : function ()
{
var self = IA;
if (self.cover && !self.cover_visible) {
Line 2,991 ⟶ 3,053:
},
 
hide_cover : function ()
{
var self = IA;
if (self.cover && self.cover_visible) {
Line 3,004 ⟶ 3,067:
},
 
getRawItem : function (what, scope)
{
var node = null;
if (!scope || scope === document) {
node = LAPI.$ ('image_annotation_' + what);
} else {
node = scope.getElementsByClassName (scope, '*', 'image_annotation_' + what);
if (node && node.length) node = node[0]; else node = null;
}
Line 3,015 ⟶ 3,079:
},
 
getItem : function (what, scope)
{
var node = IA.getRawItem(what, scope);
if (!node) return null;
Line 3,021 ⟶ 3,086:
},
 
getIntItem : function (what, scope)
/** @return {number|null} */
{
getIntItem: function (what, scope) {
var x = IA.getItem(what, scope);
if (x !== null) x = parseInt (x, 10);
Line 3,028 ⟶ 3,093:
},
 
findNote : function (text, id)
{
function find (text, id, delim) {
var start = delim.start.replace('$1', id);
Line 3,046 ⟶ 3,112:
},
 
setWikitext : function (pagetext)
{
var self = IA;
if (self.wiki_read) return;
Line 3,057 ⟶ 3,124:
for (var i = 0; i < self.note_delim.length; i++) {
var start = self.note_delim[i].content_start.replace('$1', note.model.id);
var end = self.note_delim[i].content_end.replace('$1', note.model.id);
var j = code.indexOf(start);
var k = code.indexOf(end);
Line 3,070 ⟶ 3,137:
},
 
setSummary : function (summary, initial_text, note_text)
{
if (initial_text.contains('$1')) {
var max = (summary.maxlength || 200) - initial_text.length;
Line 3,082 ⟶ 3,150:
},
 
getScript : function (url, bypass_local_cache, bypass_caches)
{
// Don't use LAPI here, it may not yet be available
if (bypass_caches) {
Line 3,100 ⟶ 3,169:
},
 
canEdit : function ()
{
var self = IA;
if (self.may_edit) {
Line 3,119 ⟶ 3,189:
}
 
}; // end IA (private)
 
// Backwards compatibility
function getElementsByClassName (scope, tag, className) {
if (window.jQuery) {
return jQuery(scope).find(((!tag || tag === '*') ? '' : tag) + '.' + className);
} else {
// For non-WMF wikis that might not have jQuery (yet), use the wikibits.js getElementsByClassName
return getElementsByClassName(scope, tag, className);
}
}
 
// Public interface
window.ImageAnnotator = {
install: function (config) { IA.install(config); }
mw.loader.using(['mediawiki.util'], function () {
IA.install(config);
});
}
};
 
// Start it. Bypass caches; but allow for 4 hours client-side caching. Small file.
IA.getScript(
mw.util.wikiScript() wgScript + '?title=MediaWiki:ImageAnnotatorConfig.js&action=raw&ctype=text/javascript'
+ '&dummy=' + Math.floor((new Date()).getTime() / (14400 * 1000)) // 4 hours
// Cache 4 hours
, true // No local caching!
+ '&dummy=' + Math.floor((new Date()).getTime() / (14400 * 1000)),
// No local caching!
true
);